/* * Written by Doug Lea and Martin Buchholz * with assistance from members of JCP JSR-166 Expert Group and * released to the public domain, as explained at * http://creativecommons.org/publicdomain/zero/1.0/ */ import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.locks.StampedLock.isLockStamp; import static java.util.concurrent.locks.StampedLock.isOptimisticReadStamp; import static java.util.concurrent.locks.StampedLock.isReadLockStamp; import static java.util.concurrent.locks.StampedLock.isWriteLockStamp; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.StampedLock; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; import junit.framework.Test; import junit.framework.TestSuite; public class StampedLockTest extends JSR166TestCase { public static void main(String[] args) { main(suite(), args); } public static Test suite() { return new TestSuite(StampedLockTest.class); } /** * Releases write lock, checking isWriteLocked before and after */ void releaseWriteLock(StampedLock lock, long stamp) { assertTrue(lock.isWriteLocked()); assertValid(lock, stamp); lock.unlockWrite(stamp); assertFalse(lock.isWriteLocked()); assertFalse(lock.validate(stamp)); } /** * Releases read lock, checking isReadLocked before and after */ void releaseReadLock(StampedLock lock, long stamp) { assertTrue(lock.isReadLocked()); assertValid(lock, stamp); lock.unlockRead(stamp); assertFalse(lock.isReadLocked()); assertTrue(lock.validate(stamp)); } long assertNonZero(long v) { assertTrue(v != 0L); return v; } long assertValid(StampedLock lock, long stamp) { assertTrue(stamp != 0L); assertTrue(lock.validate(stamp)); return stamp; } void assertUnlocked(StampedLock lock) { assertFalse(lock.isReadLocked()); assertFalse(lock.isWriteLocked()); assertEquals(0, lock.getReadLockCount()); assertValid(lock, lock.tryOptimisticRead()); } List lockLockers(Lock lock) { return List.of( () -> lock.lock(), () -> lock.lockInterruptibly(), () -> lock.tryLock(), () -> lock.tryLock(Long.MIN_VALUE, DAYS), () -> lock.tryLock(0L, DAYS), () -> lock.tryLock(Long.MAX_VALUE, DAYS)); } List> readLockers() { return List.of( sl -> sl.readLock(), sl -> sl.tryReadLock(), sl -> readLockInterruptiblyUninterrupted(sl), sl -> tryReadLockUninterrupted(sl, Long.MIN_VALUE, DAYS), sl -> tryReadLockUninterrupted(sl, 0L, DAYS), sl -> sl.tryConvertToReadLock(sl.tryOptimisticRead())); } List> readUnlockers() { return List.of( (sl, stamp) -> sl.unlockRead(stamp), (sl, stamp) -> assertTrue(sl.tryUnlockRead()), (sl, stamp) -> sl.asReadLock().unlock(), (sl, stamp) -> sl.unlock(stamp), (sl, stamp) -> assertValid(sl, sl.tryConvertToOptimisticRead(stamp))); } List> writeLockers() { return List.of( sl -> sl.writeLock(), sl -> sl.tryWriteLock(), sl -> writeLockInterruptiblyUninterrupted(sl), sl -> tryWriteLockUninterrupted(sl, Long.MIN_VALUE, DAYS), sl -> tryWriteLockUninterrupted(sl, 0L, DAYS), sl -> sl.tryConvertToWriteLock(sl.tryOptimisticRead())); } List> writeUnlockers() { return List.of( (sl, stamp) -> sl.unlockWrite(stamp), (sl, stamp) -> assertTrue(sl.tryUnlockWrite()), (sl, stamp) -> sl.asWriteLock().unlock(), (sl, stamp) -> sl.unlock(stamp), (sl, stamp) -> assertValid(sl, sl.tryConvertToOptimisticRead(stamp))); } /** * Constructed StampedLock is in unlocked state */ public void testConstructor() { assertUnlocked(new StampedLock()); } /** * write-locking, then unlocking, an unlocked lock succeed */ public void testWriteLock_lockUnlock() { StampedLock lock = new StampedLock(); for (Function writeLocker : writeLockers()) for (BiConsumer writeUnlocker : writeUnlockers()) { assertFalse(lock.isWriteLocked()); assertFalse(lock.isReadLocked()); assertEquals(0, lock.getReadLockCount()); long s = writeLocker.apply(lock); assertValid(lock, s); assertTrue(lock.isWriteLocked()); assertFalse(lock.isReadLocked()); assertEquals(0, lock.getReadLockCount()); writeUnlocker.accept(lock, s); assertUnlocked(lock); } } /** * read-locking, then unlocking, an unlocked lock succeed */ public void testReadLock_lockUnlock() { StampedLock lock = new StampedLock(); for (Function readLocker : readLockers()) for (BiConsumer readUnlocker : readUnlockers()) { long s = 42; for (int i = 0; i < 2; i++) { s = assertValid(lock, readLocker.apply(lock)); assertFalse(lock.isWriteLocked()); assertTrue(lock.isReadLocked()); assertEquals(i + 1, lock.getReadLockCount()); } for (int i = 0; i < 2; i++) { assertFalse(lock.isWriteLocked()); assertTrue(lock.isReadLocked()); assertEquals(2 - i, lock.getReadLockCount()); readUnlocker.accept(lock, s); } assertUnlocked(lock); } } /** * tryUnlockWrite fails if not write locked */ public void testTryUnlockWrite_failure() { StampedLock lock = new StampedLock(); assertFalse(lock.tryUnlockWrite()); for (Function readLocker : readLockers()) for (BiConsumer readUnlocker : readUnlockers()) { long s = assertValid(lock, readLocker.apply(lock)); assertFalse(lock.tryUnlockWrite()); assertTrue(lock.isReadLocked()); readUnlocker.accept(lock, s); assertUnlocked(lock); } } /** * tryUnlockRead fails if not read locked */ public void testTryUnlockRead_failure() { StampedLock lock = new StampedLock(); assertFalse(lock.tryUnlockRead()); for (Function writeLocker : writeLockers()) for (BiConsumer writeUnlocker : writeUnlockers()) { long s = writeLocker.apply(lock); assertFalse(lock.tryUnlockRead()); assertTrue(lock.isWriteLocked()); writeUnlocker.accept(lock, s); assertUnlocked(lock); } } /** * validate(0L) fails */ public void testValidate0() { StampedLock lock = new StampedLock(); assertFalse(lock.validate(0L)); } /** * A stamp obtained from a successful lock operation validates while the lock is held */ public void testValidate() throws InterruptedException { StampedLock lock = new StampedLock(); for (Function readLocker : readLockers()) for (BiConsumer readUnlocker : readUnlockers()) { long s = assertNonZero(readLocker.apply(lock)); assertTrue(lock.validate(s)); readUnlocker.accept(lock, s); } for (Function writeLocker : writeLockers()) for (BiConsumer writeUnlocker : writeUnlockers()) { long s = assertNonZero(writeLocker.apply(lock)); assertTrue(lock.validate(s)); writeUnlocker.accept(lock, s); } } /** * A stamp obtained from an unsuccessful lock operation does not validate */ public void testValidate2() throws InterruptedException { StampedLock lock = new StampedLock(); long s = assertNonZero(lock.writeLock()); assertTrue(lock.validate(s)); assertFalse(lock.validate(lock.tryWriteLock())); assertFalse(lock.validate(lock.tryWriteLock(randomExpiredTimeout(), randomTimeUnit()))); assertFalse(lock.validate(lock.tryReadLock())); assertFalse(lock.validate(lock.tryWriteLock(randomExpiredTimeout(), randomTimeUnit()))); assertFalse(lock.validate(lock.tryOptimisticRead())); lock.unlockWrite(s); } void assertThrowInterruptedExceptionWhenPreInterrupted(Action[] actions) { for (Action action : actions) { Thread.currentThread().interrupt(); try { action.run(); shouldThrow(); } catch (InterruptedException success) {} catch (Throwable fail) { threadUnexpectedException(fail); } assertFalse(Thread.interrupted()); } } /** * interruptible operations throw InterruptedException when pre-interrupted */ public void testInterruptibleOperationsThrowInterruptedExceptionWhenPreInterrupted() { final StampedLock lock = new StampedLock(); Action[] interruptibleLockActions = { () -> lock.writeLockInterruptibly(), () -> lock.tryWriteLock(Long.MIN_VALUE, DAYS), () -> lock.tryWriteLock(Long.MAX_VALUE, DAYS), () -> lock.readLockInterruptibly(), () -> lock.tryReadLock(Long.MIN_VALUE, DAYS), () -> lock.tryReadLock(Long.MAX_VALUE, DAYS), () -> lock.asWriteLock().lockInterruptibly(), () -> lock.asWriteLock().tryLock(0L, DAYS), () -> lock.asWriteLock().tryLock(Long.MAX_VALUE, DAYS), () -> lock.asReadLock().lockInterruptibly(), () -> lock.asReadLock().tryLock(0L, DAYS), () -> lock.asReadLock().tryLock(Long.MAX_VALUE, DAYS), }; shuffle(interruptibleLockActions); assertThrowInterruptedExceptionWhenPreInterrupted(interruptibleLockActions); { long s = lock.writeLock(); assertThrowInterruptedExceptionWhenPreInterrupted(interruptibleLockActions); lock.unlockWrite(s); } { long s = lock.readLock(); assertThrowInterruptedExceptionWhenPreInterrupted(interruptibleLockActions); lock.unlockRead(s); } } void assertThrowInterruptedExceptionWhenInterrupted(Action[] actions) { int n = actions.length; Future[] futures = new Future[n]; CountDownLatch threadsStarted = new CountDownLatch(n); CountDownLatch done = new CountDownLatch(n); for (int i = 0; i < n; i++) { Action action = actions[i]; futures[i] = cachedThreadPool.submit(new CheckedRunnable() { public void realRun() throws Throwable { threadsStarted.countDown(); try { action.run(); shouldThrow(); } catch (InterruptedException success) {} catch (Throwable fail) { threadUnexpectedException(fail); } assertFalse(Thread.interrupted()); done.countDown(); }}); } await(threadsStarted); assertEquals(n, done.getCount()); for (Future future : futures) // Interrupt all the tasks future.cancel(true); await(done); } /** * interruptible operations throw InterruptedException when write locked and interrupted */ public void testInterruptibleOperationsThrowInterruptedExceptionWriteLockedInterrupted() { final StampedLock lock = new StampedLock(); long stamp = lock.writeLock(); Action[] interruptibleLockBlockingActions = { () -> lock.writeLockInterruptibly(), () -> lock.tryWriteLock(Long.MAX_VALUE, DAYS), () -> lock.readLockInterruptibly(), () -> lock.tryReadLock(Long.MAX_VALUE, DAYS), () -> lock.asWriteLock().lockInterruptibly(), () -> lock.asWriteLock().tryLock(Long.MAX_VALUE, DAYS), () -> lock.asReadLock().lockInterruptibly(), () -> lock.asReadLock().tryLock(Long.MAX_VALUE, DAYS), }; shuffle(interruptibleLockBlockingActions); assertThrowInterruptedExceptionWhenInterrupted(interruptibleLockBlockingActions); releaseWriteLock(lock, stamp); } /** * interruptible operations throw InterruptedException when read locked and interrupted */ public void testInterruptibleOperationsThrowInterruptedExceptionReadLockedInterrupted() { final StampedLock lock = new StampedLock(); long stamp = lock.readLock(); Action[] interruptibleLockBlockingActions = { () -> lock.writeLockInterruptibly(), () -> lock.tryWriteLock(Long.MAX_VALUE, DAYS), () -> lock.asWriteLock().lockInterruptibly(), () -> lock.asWriteLock().tryLock(Long.MAX_VALUE, DAYS), }; shuffle(interruptibleLockBlockingActions); assertThrowInterruptedExceptionWhenInterrupted(interruptibleLockBlockingActions); releaseReadLock(lock, stamp); } /** * Non-interruptible operations ignore and preserve interrupt status */ public void testNonInterruptibleOperationsIgnoreInterrupts() { final StampedLock lock = new StampedLock(); Thread.currentThread().interrupt(); for (BiConsumer readUnlocker : readUnlockers()) { long s = assertValid(lock, lock.readLock()); readUnlocker.accept(lock, s); s = assertValid(lock, lock.tryReadLock()); readUnlocker.accept(lock, s); } lock.asReadLock().lock(); lock.asReadLock().unlock(); for (BiConsumer writeUnlocker : writeUnlockers()) { long s = assertValid(lock, lock.writeLock()); writeUnlocker.accept(lock, s); s = assertValid(lock, lock.tryWriteLock()); writeUnlocker.accept(lock, s); } lock.asWriteLock().lock(); lock.asWriteLock().unlock(); assertTrue(Thread.interrupted()); } /** * tryWriteLock on an unlocked lock succeeds */ public void testTryWriteLock() { final StampedLock lock = new StampedLock(); long s = lock.tryWriteLock(); assertTrue(s != 0L); assertTrue(lock.isWriteLocked()); assertEquals(0L, lock.tryWriteLock()); releaseWriteLock(lock, s); } /** * tryWriteLock fails if locked */ public void testTryWriteLockWhenLocked() { final StampedLock lock = new StampedLock(); long s = lock.writeLock(); Thread t = newStartedThread(new CheckedRunnable() { public void realRun() { assertEquals(0L, lock.tryWriteLock()); }}); assertEquals(0L, lock.tryWriteLock()); awaitTermination(t); releaseWriteLock(lock, s); } /** * tryReadLock fails if write-locked */ public void testTryReadLockWhenLocked() { final StampedLock lock = new StampedLock(); long s = lock.writeLock(); Thread t = newStartedThread(new CheckedRunnable() { public void realRun() { assertEquals(0L, lock.tryReadLock()); }}); assertEquals(0L, lock.tryReadLock()); awaitTermination(t); releaseWriteLock(lock, s); } /** * Multiple threads can hold a read lock when not write-locked */ public void testMultipleReadLocks() { final StampedLock lock = new StampedLock(); final long s = lock.readLock(); Thread t = newStartedThread(new CheckedRunnable() { public void realRun() throws InterruptedException { long s2 = lock.tryReadLock(); assertValid(lock, s2); lock.unlockRead(s2); long s3 = lock.tryReadLock(LONG_DELAY_MS, MILLISECONDS); assertValid(lock, s3); lock.unlockRead(s3); long s4 = lock.readLock(); assertValid(lock, s4); lock.unlockRead(s4); lock.asReadLock().lock(); lock.asReadLock().unlock(); lock.asReadLock().lockInterruptibly(); lock.asReadLock().unlock(); lock.asReadLock().tryLock(Long.MIN_VALUE, DAYS); lock.asReadLock().unlock(); }}); awaitTermination(t); lock.unlockRead(s); } /** * writeLock() succeeds only after a reading thread unlocks */ public void testWriteAfterReadLock() throws InterruptedException { final CountDownLatch aboutToLock = new CountDownLatch(1); final StampedLock lock = new StampedLock(); long rs = lock.readLock(); Thread t = newStartedThread(new CheckedRunnable() { public void realRun() { aboutToLock.countDown(); long s = lock.writeLock(); assertTrue(lock.isWriteLocked()); assertFalse(lock.isReadLocked()); lock.unlockWrite(s); }}); await(aboutToLock); assertThreadBlocks(t, Thread.State.WAITING); assertFalse(lock.isWriteLocked()); assertTrue(lock.isReadLocked()); lock.unlockRead(rs); awaitTermination(t); assertUnlocked(lock); } /** * writeLock() succeeds only after reading threads unlock */ public void testWriteAfterMultipleReadLocks() { final StampedLock lock = new StampedLock(); long s = lock.readLock(); Thread t1 = newStartedThread(new CheckedRunnable() { public void realRun() { long rs = lock.readLock(); lock.unlockRead(rs); }}); awaitTermination(t1); Thread t2 = newStartedThread(new CheckedRunnable() { public void realRun() { long ws = lock.writeLock(); lock.unlockWrite(ws); }}); assertTrue(lock.isReadLocked()); assertFalse(lock.isWriteLocked()); lock.unlockRead(s); awaitTermination(t2); assertUnlocked(lock); } /** * readLock() succeed only after a writing thread unlocks */ public void testReadAfterWriteLock() { final StampedLock lock = new StampedLock(); final CountDownLatch threadsStarted = new CountDownLatch(2); final long s = lock.writeLock(); final Runnable acquireReleaseReadLock = new CheckedRunnable() { public void realRun() { threadsStarted.countDown(); long rs = lock.readLock(); assertTrue(lock.isReadLocked()); assertFalse(lock.isWriteLocked()); lock.unlockRead(rs); }}; Thread t1 = newStartedThread(acquireReleaseReadLock); Thread t2 = newStartedThread(acquireReleaseReadLock); await(threadsStarted); assertThreadBlocks(t1, Thread.State.WAITING); assertThreadBlocks(t2, Thread.State.WAITING); assertTrue(lock.isWriteLocked()); assertFalse(lock.isReadLocked()); releaseWriteLock(lock, s); awaitTermination(t1); awaitTermination(t2); assertUnlocked(lock); } /** * tryReadLock succeeds if read locked but not write locked */ public void testTryLockWhenReadLocked() { final StampedLock lock = new StampedLock(); long s = lock.readLock(); Thread t = newStartedThread(new CheckedRunnable() { public void realRun() { long rs = lock.tryReadLock(); assertValid(lock, rs); lock.unlockRead(rs); }}); awaitTermination(t); lock.unlockRead(s); } /** * tryWriteLock fails when read locked */ public void testTryWriteLockWhenReadLocked() { final StampedLock lock = new StampedLock(); long s = lock.readLock(); Thread t = newStartedThread(new CheckedRunnable() { public void realRun() { assertEquals(0L, lock.tryWriteLock()); }}); awaitTermination(t); lock.unlockRead(s); } /** * timed lock operations time out if lock not available */ public void testTimedLock_Timeout() throws Exception { ArrayList> futures = new ArrayList<>(); // Write locked final StampedLock lock = new StampedLock(); long stamp = lock.writeLock(); assertEquals(0L, lock.tryReadLock(0L, DAYS)); assertEquals(0L, lock.tryReadLock(Long.MIN_VALUE, DAYS)); assertFalse(lock.asReadLock().tryLock(0L, DAYS)); assertFalse(lock.asReadLock().tryLock(Long.MIN_VALUE, DAYS)); assertEquals(0L, lock.tryWriteLock(0L, DAYS)); assertEquals(0L, lock.tryWriteLock(Long.MIN_VALUE, DAYS)); assertFalse(lock.asWriteLock().tryLock(0L, DAYS)); assertFalse(lock.asWriteLock().tryLock(Long.MIN_VALUE, DAYS)); futures.add(cachedThreadPool.submit(new CheckedRunnable() { public void realRun() throws InterruptedException { long startTime = System.nanoTime(); assertEquals(0L, lock.tryWriteLock(timeoutMillis(), MILLISECONDS)); assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); }})); futures.add(cachedThreadPool.submit(new CheckedRunnable() { public void realRun() throws InterruptedException { long startTime = System.nanoTime(); assertEquals(0L, lock.tryReadLock(timeoutMillis(), MILLISECONDS)); assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); }})); // Read locked final StampedLock lock2 = new StampedLock(); long stamp2 = lock2.readLock(); assertEquals(0L, lock2.tryWriteLock(0L, DAYS)); assertEquals(0L, lock2.tryWriteLock(Long.MIN_VALUE, DAYS)); assertFalse(lock2.asWriteLock().tryLock(0L, DAYS)); assertFalse(lock2.asWriteLock().tryLock(Long.MIN_VALUE, DAYS)); futures.add(cachedThreadPool.submit(new CheckedRunnable() { public void realRun() throws InterruptedException { long startTime = System.nanoTime(); assertEquals(0L, lock2.tryWriteLock(timeoutMillis(), MILLISECONDS)); assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); }})); for (Future future : futures) assertNull(future.get()); releaseWriteLock(lock, stamp); releaseReadLock(lock2, stamp2); } /** * writeLockInterruptibly succeeds if unlocked */ public void testWriteLockInterruptibly() throws InterruptedException { final StampedLock lock = new StampedLock(); long s = lock.writeLockInterruptibly(); assertTrue(lock.isWriteLocked()); releaseWriteLock(lock, s); } /** * readLockInterruptibly succeeds if lock free */ public void testReadLockInterruptibly() throws InterruptedException { final StampedLock lock = new StampedLock(); long s = assertValid(lock, lock.readLockInterruptibly()); assertTrue(lock.isReadLocked()); lock.unlockRead(s); lock.asReadLock().lockInterruptibly(); assertTrue(lock.isReadLocked()); lock.asReadLock().unlock(); } /** * A serialized lock deserializes as unlocked */ public void testSerialization() { StampedLock lock = new StampedLock(); lock.writeLock(); StampedLock clone = serialClone(lock); assertTrue(lock.isWriteLocked()); assertFalse(clone.isWriteLocked()); long s = clone.writeLock(); assertTrue(clone.isWriteLocked()); clone.unlockWrite(s); assertFalse(clone.isWriteLocked()); } /** * toString indicates current lock state */ public void testToString() { StampedLock lock = new StampedLock(); assertTrue(lock.toString().contains("Unlocked")); long s = lock.writeLock(); assertTrue(lock.toString().contains("Write-locked")); lock.unlockWrite(s); s = lock.readLock(); assertTrue(lock.toString().contains("Read-locks")); releaseReadLock(lock, s); } /** * tryOptimisticRead succeeds and validates if unlocked, fails if * exclusively locked */ public void testValidateOptimistic() throws InterruptedException { StampedLock lock = new StampedLock(); assertValid(lock, lock.tryOptimisticRead()); for (Function writeLocker : writeLockers()) { long s = assertValid(lock, writeLocker.apply(lock)); assertEquals(0L, lock.tryOptimisticRead()); releaseWriteLock(lock, s); } for (Function readLocker : readLockers()) { long s = assertValid(lock, readLocker.apply(lock)); long p = assertValid(lock, lock.tryOptimisticRead()); releaseReadLock(lock, s); assertTrue(lock.validate(p)); } assertValid(lock, lock.tryOptimisticRead()); } /** * tryOptimisticRead stamp does not validate if a write lock intervenes */ public void testValidateOptimisticWriteLocked() { final StampedLock lock = new StampedLock(); final long p = assertValid(lock, lock.tryOptimisticRead()); final long s = assertValid(lock, lock.writeLock()); assertFalse(lock.validate(p)); assertEquals(0L, lock.tryOptimisticRead()); assertTrue(lock.validate(s)); lock.unlockWrite(s); } /** * tryOptimisticRead stamp does not validate if a write lock * intervenes in another thread */ public void testValidateOptimisticWriteLocked2() throws InterruptedException { final CountDownLatch locked = new CountDownLatch(1); final StampedLock lock = new StampedLock(); final long p = assertValid(lock, lock.tryOptimisticRead()); Thread t = newStartedThread(new CheckedInterruptedRunnable() { public void realRun() throws InterruptedException { lock.writeLockInterruptibly(); locked.countDown(); lock.writeLockInterruptibly(); }}); await(locked); assertFalse(lock.validate(p)); assertEquals(0L, lock.tryOptimisticRead()); assertThreadBlocks(t, Thread.State.WAITING); t.interrupt(); awaitTermination(t); assertTrue(lock.isWriteLocked()); } /** * tryConvertToOptimisticRead succeeds and validates if successfully locked */ public void testTryConvertToOptimisticRead() throws InterruptedException { StampedLock lock = new StampedLock(); long s, p, q; assertEquals(0L, lock.tryConvertToOptimisticRead(0L)); s = assertValid(lock, lock.tryOptimisticRead()); assertEquals(s, lock.tryConvertToOptimisticRead(s)); assertTrue(lock.validate(s)); for (Function writeLocker : writeLockers()) { s = assertValid(lock, writeLocker.apply(lock)); p = assertValid(lock, lock.tryConvertToOptimisticRead(s)); assertFalse(lock.validate(s)); assertTrue(lock.validate(p)); assertUnlocked(lock); } for (Function readLocker : readLockers()) { s = assertValid(lock, readLocker.apply(lock)); q = assertValid(lock, lock.tryOptimisticRead()); assertEquals(q, lock.tryConvertToOptimisticRead(q)); assertTrue(lock.validate(q)); assertTrue(lock.isReadLocked()); p = assertValid(lock, lock.tryConvertToOptimisticRead(s)); assertTrue(lock.validate(p)); assertTrue(lock.validate(s)); assertUnlocked(lock); assertEquals(q, lock.tryConvertToOptimisticRead(q)); assertTrue(lock.validate(q)); } } /** * tryConvertToReadLock succeeds for valid stamps */ public void testTryConvertToReadLock() throws InterruptedException { StampedLock lock = new StampedLock(); long s, p; assertEquals(0L, lock.tryConvertToReadLock(0L)); s = assertValid(lock, lock.tryOptimisticRead()); p = assertValid(lock, lock.tryConvertToReadLock(s)); assertTrue(lock.isReadLocked()); assertEquals(1, lock.getReadLockCount()); assertTrue(lock.validate(s)); lock.unlockRead(p); s = assertValid(lock, lock.tryOptimisticRead()); lock.readLock(); p = assertValid(lock, lock.tryConvertToReadLock(s)); assertTrue(lock.isReadLocked()); assertEquals(2, lock.getReadLockCount()); lock.unlockRead(p); lock.unlockRead(p); assertUnlocked(lock); for (BiConsumer readUnlocker : readUnlockers()) { for (Function writeLocker : writeLockers()) { s = assertValid(lock, writeLocker.apply(lock)); p = assertValid(lock, lock.tryConvertToReadLock(s)); assertFalse(lock.validate(s)); assertTrue(lock.isReadLocked()); assertEquals(1, lock.getReadLockCount()); readUnlocker.accept(lock, p); } for (Function readLocker : readLockers()) { s = assertValid(lock, readLocker.apply(lock)); assertEquals(s, lock.tryConvertToReadLock(s)); assertTrue(lock.validate(s)); assertTrue(lock.isReadLocked()); assertEquals(1, lock.getReadLockCount()); readUnlocker.accept(lock, s); } } } /** * tryConvertToWriteLock succeeds if lock available; fails if multiply read locked */ public void testTryConvertToWriteLock() throws InterruptedException { StampedLock lock = new StampedLock(); long s, p; assertEquals(0L, lock.tryConvertToWriteLock(0L)); assertTrue((s = lock.tryOptimisticRead()) != 0L); assertTrue((p = lock.tryConvertToWriteLock(s)) != 0L); assertTrue(lock.isWriteLocked()); lock.unlockWrite(p); for (BiConsumer writeUnlocker : writeUnlockers()) { for (Function writeLocker : writeLockers()) { s = assertValid(lock, writeLocker.apply(lock)); assertEquals(s, lock.tryConvertToWriteLock(s)); assertTrue(lock.validate(s)); assertTrue(lock.isWriteLocked()); writeUnlocker.accept(lock, s); } for (Function readLocker : readLockers()) { s = assertValid(lock, readLocker.apply(lock)); p = assertValid(lock, lock.tryConvertToWriteLock(s)); assertFalse(lock.validate(s)); assertTrue(lock.validate(p)); assertTrue(lock.isWriteLocked()); writeUnlocker.accept(lock, p); } } // failure if multiply read locked for (Function readLocker : readLockers()) { s = assertValid(lock, readLocker.apply(lock)); p = assertValid(lock, readLocker.apply(lock)); assertEquals(0L, lock.tryConvertToWriteLock(s)); assertTrue(lock.validate(s)); assertTrue(lock.validate(p)); assertEquals(2, lock.getReadLockCount()); lock.unlock(p); lock.unlock(s); assertUnlocked(lock); } } /** * asWriteLock can be locked and unlocked */ public void testAsWriteLock() throws Throwable { StampedLock sl = new StampedLock(); Lock lock = sl.asWriteLock(); for (Action locker : lockLockers(lock)) { locker.run(); assertTrue(sl.isWriteLocked()); assertFalse(sl.isReadLocked()); assertFalse(lock.tryLock()); lock.unlock(); assertUnlocked(sl); } } /** * asReadLock can be locked and unlocked */ public void testAsReadLock() throws Throwable { StampedLock sl = new StampedLock(); Lock lock = sl.asReadLock(); for (Action locker : lockLockers(lock)) { locker.run(); assertTrue(sl.isReadLocked()); assertFalse(sl.isWriteLocked()); assertEquals(1, sl.getReadLockCount()); locker.run(); assertTrue(sl.isReadLocked()); assertEquals(2, sl.getReadLockCount()); lock.unlock(); lock.unlock(); assertUnlocked(sl); } } /** * asReadWriteLock.writeLock can be locked and unlocked */ public void testAsReadWriteLockWriteLock() throws Throwable { StampedLock sl = new StampedLock(); Lock lock = sl.asReadWriteLock().writeLock(); for (Action locker : lockLockers(lock)) { locker.run(); assertTrue(sl.isWriteLocked()); assertFalse(sl.isReadLocked()); assertFalse(lock.tryLock()); lock.unlock(); assertUnlocked(sl); } } /** * asReadWriteLock.readLock can be locked and unlocked */ public void testAsReadWriteLockReadLock() throws Throwable { StampedLock sl = new StampedLock(); Lock lock = sl.asReadWriteLock().readLock(); for (Action locker : lockLockers(lock)) { locker.run(); assertTrue(sl.isReadLocked()); assertFalse(sl.isWriteLocked()); assertEquals(1, sl.getReadLockCount()); locker.run(); assertTrue(sl.isReadLocked()); assertEquals(2, sl.getReadLockCount()); lock.unlock(); lock.unlock(); assertUnlocked(sl); } } /** * Lock.newCondition throws UnsupportedOperationException */ public void testLockViewsDoNotSupportConditions() { StampedLock sl = new StampedLock(); assertThrows(UnsupportedOperationException.class, () -> sl.asWriteLock().newCondition(), () -> sl.asReadLock().newCondition(), () -> sl.asReadWriteLock().writeLock().newCondition(), () -> sl.asReadWriteLock().readLock().newCondition()); } /** * Passing optimistic read stamps to unlock operations result in * IllegalMonitorStateException */ public void testCannotUnlockOptimisticReadStamps() { { StampedLock sl = new StampedLock(); long stamp = assertValid(sl, sl.tryOptimisticRead()); assertThrows(IllegalMonitorStateException.class, () -> sl.unlockRead(stamp)); } { StampedLock sl = new StampedLock(); long stamp = sl.tryOptimisticRead(); assertThrows(IllegalMonitorStateException.class, () -> sl.unlock(stamp)); } { StampedLock sl = new StampedLock(); long stamp = sl.tryOptimisticRead(); sl.writeLock(); assertThrows(IllegalMonitorStateException.class, () -> sl.unlock(stamp)); } { StampedLock sl = new StampedLock(); sl.readLock(); long stamp = assertValid(sl, sl.tryOptimisticRead()); assertThrows(IllegalMonitorStateException.class, () -> sl.unlockRead(stamp)); } { StampedLock sl = new StampedLock(); sl.readLock(); long stamp = assertValid(sl, sl.tryOptimisticRead()); assertThrows(IllegalMonitorStateException.class, () -> sl.unlock(stamp)); } { StampedLock sl = new StampedLock(); long stamp = sl.tryConvertToOptimisticRead(sl.writeLock()); assertValid(sl, stamp); sl.writeLock(); assertThrows(IllegalMonitorStateException.class, () -> sl.unlockWrite(stamp)); } { StampedLock sl = new StampedLock(); long stamp = sl.tryConvertToOptimisticRead(sl.writeLock()); sl.writeLock(); assertThrows(IllegalMonitorStateException.class, () -> sl.unlock(stamp)); } { StampedLock sl = new StampedLock(); long stamp = sl.tryConvertToOptimisticRead(sl.writeLock()); sl.readLock(); assertThrows(IllegalMonitorStateException.class, () -> sl.unlockRead(stamp)); } { StampedLock sl = new StampedLock(); long stamp = sl.tryConvertToOptimisticRead(sl.writeLock()); sl.readLock(); assertThrows(IllegalMonitorStateException.class, () -> sl.unlock(stamp)); } { StampedLock sl = new StampedLock(); long stamp = sl.tryConvertToOptimisticRead(sl.readLock()); assertValid(sl, stamp); sl.writeLock(); assertThrows(IllegalMonitorStateException.class, () -> sl.unlockWrite(stamp)); } { StampedLock sl = new StampedLock(); long stamp = sl.tryConvertToOptimisticRead(sl.readLock()); sl.writeLock(); assertThrows(IllegalMonitorStateException.class, () -> sl.unlock(stamp)); } { StampedLock sl = new StampedLock(); long stamp = sl.tryConvertToOptimisticRead(sl.readLock()); sl.readLock(); assertThrows(IllegalMonitorStateException.class, () -> sl.unlockRead(stamp)); } { StampedLock sl = new StampedLock(); sl.readLock(); long stamp = sl.tryConvertToOptimisticRead(sl.readLock()); assertValid(sl, stamp); sl.readLock(); assertThrows(IllegalMonitorStateException.class, () -> sl.unlockRead(stamp)); } { StampedLock sl = new StampedLock(); long stamp = sl.tryConvertToOptimisticRead(sl.readLock()); sl.readLock(); assertThrows(IllegalMonitorStateException.class, () -> sl.unlock(stamp)); } { StampedLock sl = new StampedLock(); sl.readLock(); long stamp = sl.tryConvertToOptimisticRead(sl.readLock()); sl.readLock(); assertThrows(IllegalMonitorStateException.class, () -> sl.unlock(stamp)); } } static long writeLockInterruptiblyUninterrupted(StampedLock sl) { try { return sl.writeLockInterruptibly(); } catch (InterruptedException ex) { throw new AssertionError(ex); } } static long tryWriteLockUninterrupted(StampedLock sl, long time, TimeUnit unit) { try { return sl.tryWriteLock(time, unit); } catch (InterruptedException ex) { throw new AssertionError(ex); } } static long readLockInterruptiblyUninterrupted(StampedLock sl) { try { return sl.readLockInterruptibly(); } catch (InterruptedException ex) { throw new AssertionError(ex); } } static long tryReadLockUninterrupted(StampedLock sl, long time, TimeUnit unit) { try { return sl.tryReadLock(time, unit); } catch (InterruptedException ex) { throw new AssertionError(ex); } } /** * Invalid stamps result in IllegalMonitorStateException */ public void testInvalidStampsThrowIllegalMonitorStateException() { final StampedLock sl = new StampedLock(); assertThrows(IllegalMonitorStateException.class, () -> sl.unlockWrite(0L), () -> sl.unlockRead(0L), () -> sl.unlock(0L)); final long optimisticStamp = sl.tryOptimisticRead(); final long readStamp = sl.readLock(); sl.unlockRead(readStamp); final long writeStamp = sl.writeLock(); sl.unlockWrite(writeStamp); assertTrue(optimisticStamp != 0L && readStamp != 0L && writeStamp != 0L); final long[] noLongerValidStamps = { optimisticStamp, readStamp, writeStamp }; final Runnable assertNoLongerValidStampsThrow = () -> { for (long noLongerValidStamp : noLongerValidStamps) assertThrows(IllegalMonitorStateException.class, () -> sl.unlockWrite(noLongerValidStamp), () -> sl.unlockRead(noLongerValidStamp), () -> sl.unlock(noLongerValidStamp)); }; assertNoLongerValidStampsThrow.run(); for (Function readLocker : readLockers()) for (BiConsumer readUnlocker : readUnlockers()) { final long stamp = readLocker.apply(sl); assertValid(sl, stamp); assertNoLongerValidStampsThrow.run(); assertThrows(IllegalMonitorStateException.class, () -> sl.unlockWrite(stamp), () -> sl.unlockRead(sl.tryOptimisticRead()), () -> sl.unlockRead(0L)); readUnlocker.accept(sl, stamp); assertUnlocked(sl); assertNoLongerValidStampsThrow.run(); } for (Function writeLocker : writeLockers()) for (BiConsumer writeUnlocker : writeUnlockers()) { final long stamp = writeLocker.apply(sl); assertValid(sl, stamp); assertNoLongerValidStampsThrow.run(); assertThrows(IllegalMonitorStateException.class, () -> sl.unlockRead(stamp), () -> sl.unlockWrite(0L)); writeUnlocker.accept(sl, stamp); assertUnlocked(sl); assertNoLongerValidStampsThrow.run(); } } /** * Read locks can be very deeply nested */ public void testDeeplyNestedReadLocks() { final StampedLock lock = new StampedLock(); final int depth = 300; final long[] stamps = new long[depth]; final List> readLockers = readLockers(); final List> readUnlockers = readUnlockers(); for (int i = 0; i < depth; i++) { Function readLocker = readLockers.get(i % readLockers.size()); long stamp = readLocker.apply(lock); assertEquals(i + 1, lock.getReadLockCount()); assertTrue(lock.isReadLocked()); stamps[i] = stamp; } for (int i = 0; i < depth; i++) { BiConsumer readUnlocker = readUnlockers.get(i % readUnlockers.size()); assertEquals(depth - i, lock.getReadLockCount()); assertTrue(lock.isReadLocked()); readUnlocker.accept(lock, stamps[depth - 1 - i]); } assertUnlocked(lock); } /** * Stamped locks are not reentrant. */ public void testNonReentrant() throws InterruptedException { final StampedLock lock = new StampedLock(); long stamp; stamp = lock.writeLock(); assertValid(lock, stamp); assertEquals(0L, lock.tryWriteLock(0L, DAYS)); assertEquals(0L, lock.tryReadLock(0L, DAYS)); assertValid(lock, stamp); lock.unlockWrite(stamp); stamp = lock.tryWriteLock(1L, DAYS); assertEquals(0L, lock.tryWriteLock(0L, DAYS)); assertValid(lock, stamp); lock.unlockWrite(stamp); stamp = lock.readLock(); assertEquals(0L, lock.tryWriteLock(0L, DAYS)); assertValid(lock, stamp); lock.unlockRead(stamp); } /** * """StampedLocks have no notion of ownership. Locks acquired in * one thread can be released or converted in another.""" */ public void testNoOwnership() throws Throwable { ArrayList> futures = new ArrayList<>(); for (Function writeLocker : writeLockers()) for (BiConsumer writeUnlocker : writeUnlockers()) { StampedLock lock = new StampedLock(); long stamp = writeLocker.apply(lock); futures.add(cachedThreadPool.submit(new CheckedRunnable() { public void realRun() { writeUnlocker.accept(lock, stamp); assertUnlocked(lock); assertFalse(lock.validate(stamp)); }})); } for (Future future : futures) assertNull(future.get()); } /** Tries out sample usage code from StampedLock javadoc. */ public void testSampleUsage() throws Throwable { class Point { private double x, y; private final StampedLock sl = new StampedLock(); void move(double deltaX, double deltaY) { // an exclusively locked method long stamp = sl.writeLock(); try { x += deltaX; y += deltaY; } finally { sl.unlockWrite(stamp); } } double distanceFromOrigin() { // A read-only method double currentX, currentY; long stamp = sl.tryOptimisticRead(); do { if (stamp == 0L) stamp = sl.readLock(); try { // possibly racy reads currentX = x; currentY = y; } finally { stamp = sl.tryConvertToOptimisticRead(stamp); } } while (stamp == 0); return Math.hypot(currentX, currentY); } double distanceFromOrigin2() { long stamp = sl.tryOptimisticRead(); try { retryHoldingLock: for (;; stamp = sl.readLock()) { if (stamp == 0L) continue retryHoldingLock; // possibly racy reads double currentX = x; double currentY = y; if (!sl.validate(stamp)) continue retryHoldingLock; return Math.hypot(currentX, currentY); } } finally { if (StampedLock.isReadLockStamp(stamp)) sl.unlockRead(stamp); } } void moveIfAtOrigin(double newX, double newY) { long stamp = sl.readLock(); try { while (x == 0.0 && y == 0.0) { long ws = sl.tryConvertToWriteLock(stamp); if (ws != 0L) { stamp = ws; x = newX; y = newY; return; } else { sl.unlockRead(stamp); stamp = sl.writeLock(); } } } finally { sl.unlock(stamp); } } } Point p = new Point(); p.move(3.0, 4.0); assertEquals(5.0, p.distanceFromOrigin()); p.moveIfAtOrigin(5.0, 12.0); assertEquals(5.0, p.distanceFromOrigin2()); } /** * Stamp inspection methods work as expected, and do not inspect * the state of the lock itself. */ public void testStampStateInspectionMethods() { StampedLock lock = new StampedLock(); assertFalse(isWriteLockStamp(0L)); assertFalse(isReadLockStamp(0L)); assertFalse(isLockStamp(0L)); assertFalse(isOptimisticReadStamp(0L)); { long stamp = lock.writeLock(); for (int i = 0; i < 2; i++) { assertTrue(isWriteLockStamp(stamp)); assertFalse(isReadLockStamp(stamp)); assertTrue(isLockStamp(stamp)); assertFalse(isOptimisticReadStamp(stamp)); if (i == 0) lock.unlockWrite(stamp); } } { long stamp = lock.readLock(); for (int i = 0; i < 2; i++) { assertFalse(isWriteLockStamp(stamp)); assertTrue(isReadLockStamp(stamp)); assertTrue(isLockStamp(stamp)); assertFalse(isOptimisticReadStamp(stamp)); if (i == 0) lock.unlockRead(stamp); } } { long optimisticStamp = lock.tryOptimisticRead(); long readStamp = lock.tryConvertToReadLock(optimisticStamp); long writeStamp = lock.tryConvertToWriteLock(readStamp); for (int i = 0; i < 2; i++) { assertFalse(isWriteLockStamp(optimisticStamp)); assertFalse(isReadLockStamp(optimisticStamp)); assertFalse(isLockStamp(optimisticStamp)); assertTrue(isOptimisticReadStamp(optimisticStamp)); assertFalse(isWriteLockStamp(readStamp)); assertTrue(isReadLockStamp(readStamp)); assertTrue(isLockStamp(readStamp)); assertFalse(isOptimisticReadStamp(readStamp)); assertTrue(isWriteLockStamp(writeStamp)); assertFalse(isReadLockStamp(writeStamp)); assertTrue(isLockStamp(writeStamp)); assertFalse(isOptimisticReadStamp(writeStamp)); if (i == 0) lock.unlockWrite(writeStamp); } } } /** * Multiple threads repeatedly contend for the same lock. */ public void testConcurrentAccess() throws Exception { final StampedLock sl = new StampedLock(); final Lock wl = sl.asWriteLock(); final Lock rl = sl.asReadLock(); final long testDurationMillis = expensiveTests ? 1000 : 2; final int nTasks = ThreadLocalRandom.current().nextInt(1, 10); final AtomicBoolean done = new AtomicBoolean(false); final List> futures = new ArrayList<>(); final List> stampedWriteLockers = List.of( () -> sl.writeLock(), () -> writeLockInterruptiblyUninterrupted(sl), () -> tryWriteLockUninterrupted(sl, LONG_DELAY_MS, MILLISECONDS), () -> { long stamp; do { stamp = sl.tryConvertToWriteLock(sl.tryOptimisticRead()); } while (stamp == 0L); return stamp; }, () -> { long stamp; do { stamp = sl.tryWriteLock(); } while (stamp == 0L); return stamp; }, () -> { long stamp; do { stamp = sl.tryWriteLock(0L, DAYS); } while (stamp == 0L); return stamp; }); final List> stampedReadLockers = List.of( () -> sl.readLock(), () -> readLockInterruptiblyUninterrupted(sl), () -> tryReadLockUninterrupted(sl, LONG_DELAY_MS, MILLISECONDS), () -> { long stamp; do { stamp = sl.tryConvertToReadLock(sl.tryOptimisticRead()); } while (stamp == 0L); return stamp; }, () -> { long stamp; do { stamp = sl.tryReadLock(); } while (stamp == 0L); return stamp; }, () -> { long stamp; do { stamp = sl.tryReadLock(0L, DAYS); } while (stamp == 0L); return stamp; }); final List> stampedWriteUnlockers = List.of( stamp -> sl.unlockWrite(stamp), stamp -> sl.unlock(stamp), stamp -> assertTrue(sl.tryUnlockWrite()), stamp -> wl.unlock(), stamp -> sl.tryConvertToOptimisticRead(stamp)); final List> stampedReadUnlockers = List.of( stamp -> sl.unlockRead(stamp), stamp -> sl.unlock(stamp), stamp -> assertTrue(sl.tryUnlockRead()), stamp -> rl.unlock(), stamp -> sl.tryConvertToOptimisticRead(stamp)); final Action writer = () -> { // repeatedly acquires write lock var locker = chooseRandomly(stampedWriteLockers); var unlocker = chooseRandomly(stampedWriteUnlockers); while (!done.getAcquire()) { long stamp = locker.call(); try { assertTrue(isWriteLockStamp(stamp)); assertTrue(sl.isWriteLocked()); assertFalse(isReadLockStamp(stamp)); assertFalse(sl.isReadLocked()); assertEquals(0, sl.getReadLockCount()); assertTrue(sl.validate(stamp)); } finally { unlocker.accept(stamp); } } }; final Action reader = () -> { // repeatedly acquires read lock var locker = chooseRandomly(stampedReadLockers); var unlocker = chooseRandomly(stampedReadUnlockers); while (!done.getAcquire()) { long stamp = locker.call(); try { assertFalse(isWriteLockStamp(stamp)); assertFalse(sl.isWriteLocked()); assertTrue(isReadLockStamp(stamp)); assertTrue(sl.isReadLocked()); assertTrue(sl.getReadLockCount() > 0); assertTrue(sl.validate(stamp)); } finally { unlocker.accept(stamp); } } }; for (int i = nTasks; i--> 0; ) { Action task = chooseRandomly(writer, reader); futures.add(CompletableFuture.runAsync(checkedRunnable(task))); } Thread.sleep(testDurationMillis); done.setRelease(true); for (var future : futures) checkTimedGet(future, null); } }