/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * This file is available under and governed by the GNU General Public * License version 2 only, as published by the Free Software Foundation. * However, the following notice accompanied the original version of this * file: * * 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.TimeUnit.SECONDS; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.StampedLock; import java.util.function.BiConsumer; 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<Action> lockLockers(Lock lock) { List<Action> lockers = new ArrayList<>(); lockers.add(() -> lock.lock()); lockers.add(() -> lock.lockInterruptibly()); lockers.add(() -> lock.tryLock()); lockers.add(() -> lock.tryLock(Long.MIN_VALUE, DAYS)); lockers.add(() -> lock.tryLock(0L, DAYS)); lockers.add(() -> lock.tryLock(Long.MAX_VALUE, DAYS)); return lockers; } List<Function<StampedLock, Long>> readLockers() { List<Function<StampedLock, Long>> readLockers = new ArrayList<>(); readLockers.add(sl -> sl.readLock()); readLockers.add(sl -> sl.tryReadLock()); readLockers.add(sl -> readLockInterruptiblyUninterrupted(sl)); readLockers.add(sl -> tryReadLockUninterrupted(sl, Long.MIN_VALUE, DAYS)); readLockers.add(sl -> tryReadLockUninterrupted(sl, 0L, DAYS)); readLockers.add(sl -> sl.tryConvertToReadLock(sl.tryOptimisticRead())); return readLockers; } List<BiConsumer<StampedLock, Long>> readUnlockers() { List<BiConsumer<StampedLock, Long>> readUnlockers = new ArrayList<>(); readUnlockers.add((sl, stamp) -> sl.unlockRead(stamp)); readUnlockers.add((sl, stamp) -> assertTrue(sl.tryUnlockRead())); readUnlockers.add((sl, stamp) -> sl.asReadLock().unlock()); readUnlockers.add((sl, stamp) -> sl.unlock(stamp)); readUnlockers.add((sl, stamp) -> assertValid(sl, sl.tryConvertToOptimisticRead(stamp))); return readUnlockers; } List<Function<StampedLock, Long>> writeLockers() { List<Function<StampedLock, Long>> writeLockers = new ArrayList<>(); writeLockers.add(sl -> sl.writeLock()); writeLockers.add(sl -> sl.tryWriteLock()); writeLockers.add(sl -> writeLockInterruptiblyUninterrupted(sl)); writeLockers.add(sl -> tryWriteLockUninterrupted(sl, Long.MIN_VALUE, DAYS)); writeLockers.add(sl -> tryWriteLockUninterrupted(sl, 0L, DAYS)); writeLockers.add(sl -> sl.tryConvertToWriteLock(sl.tryOptimisticRead())); return writeLockers; } List<BiConsumer<StampedLock, Long>> writeUnlockers() { List<BiConsumer<StampedLock, Long>> writeUnlockers = new ArrayList<>(); writeUnlockers.add((sl, stamp) -> sl.unlockWrite(stamp)); writeUnlockers.add((sl, stamp) -> assertTrue(sl.tryUnlockWrite())); writeUnlockers.add((sl, stamp) -> sl.asWriteLock().unlock()); writeUnlockers.add((sl, stamp) -> sl.unlock(stamp)); writeUnlockers.add((sl, stamp) -> assertValid(sl, sl.tryConvertToOptimisticRead(stamp))); return writeUnlockers; } /** * 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<StampedLock, Long> writeLocker : writeLockers()) for (BiConsumer<StampedLock, Long> 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<StampedLock, Long> readLocker : readLockers()) for (BiConsumer<StampedLock, Long> 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<StampedLock, Long> readLocker : readLockers()) for (BiConsumer<StampedLock, Long> 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<StampedLock, Long> writeLocker : writeLockers()) for (BiConsumer<StampedLock, Long> 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<StampedLock, Long> readLocker : readLockers()) for (BiConsumer<StampedLock, Long> readUnlocker : readUnlockers()) { long s = assertNonZero(readLocker.apply(lock)); assertTrue(lock.validate(s)); readUnlocker.accept(lock, s); } for (Function<StampedLock, Long> writeLocker : writeLockers()) for (BiConsumer<StampedLock, Long> 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(0L, SECONDS))); assertFalse(lock.validate(lock.tryReadLock())); assertFalse(lock.validate(lock.tryReadLock(0L, SECONDS))); 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 CountDownLatch running = new CountDownLatch(1); 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 CountDownLatch running = new CountDownLatch(1); final StampedLock lock = new StampedLock(); long s = 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); } /** * interruptible operations throw InterruptedException when read locked and interrupted */ public void testInterruptibleOperationsThrowInterruptedExceptionReadLockedInterrupted() { final CountDownLatch running = new CountDownLatch(1); final StampedLock lock = new StampedLock(); long s = 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); } /** * Non-interruptible operations ignore and preserve interrupt status */ public void testNonInterruptibleOperationsIgnoreInterrupts() { final StampedLock lock = new StampedLock(); Thread.currentThread().interrupt(); for (BiConsumer<StampedLock, Long> 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<StampedLock, Long> 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); } /** * A writelock succeeds only after a reading thread unlocks */ public void testWriteAfterReadLock() throws InterruptedException { final CountDownLatch running = new CountDownLatch(1); final StampedLock lock = new StampedLock(); long rs = lock.readLock(); Thread t = newStartedThread(new CheckedRunnable() { public void realRun() { running.countDown(); long s = lock.writeLock(); lock.unlockWrite(s); }}); running.await(); waitForThreadToEnterWaitState(t, MEDIUM_DELAY_MS); assertFalse(lock.isWriteLocked()); lock.unlockRead(rs); awaitTermination(t); assertFalse(lock.isWriteLocked()); } /** * A 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); assertFalse(lock.isWriteLocked()); } /** * Readlocks 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(); Thread t1 = newStartedThread(new CheckedRunnable() { public void realRun() { threadsStarted.countDown(); long rs = lock.readLock(); lock.unlockRead(rs); }}); Thread t2 = newStartedThread(new CheckedRunnable() { public void realRun() { threadsStarted.countDown(); long rs = lock.readLock(); lock.unlockRead(rs); }}); await(threadsStarted); waitForThreadToEnterWaitState(t1, MEDIUM_DELAY_MS); waitForThreadToEnterWaitState(t2, MEDIUM_DELAY_MS); releaseWriteLock(lock, s); awaitTermination(t1); awaitTermination(t2); } /** * 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() { threadAssertEquals(0L, lock.tryWriteLock()); }}); awaitTermination(t); lock.unlockRead(s); } /** * timed lock operations time out if lock not available */ public void testTimedLock_Timeout() throws Exception { ArrayList<Future<?>> 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")); } /** * 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<StampedLock, Long> writeLocker : writeLockers()) { long s = assertValid(lock, writeLocker.apply(lock)); assertEquals(0L, lock.tryOptimisticRead()); releaseWriteLock(lock, s); } for (Function<StampedLock, Long> 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 running = 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(); running.countDown(); lock.writeLockInterruptibly(); }}); running.await(); assertFalse(lock.validate(p)); assertEquals(0L, lock.tryOptimisticRead()); t.interrupt(); awaitTermination(t); } /** * 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<StampedLock, Long> 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<StampedLock, Long> 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<StampedLock, Long> readUnlocker : readUnlockers()) { for (Function<StampedLock, Long> 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<StampedLock, Long> 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<StampedLock, Long> writeUnlocker : writeUnlockers()) { for (Function<StampedLock, Long> 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<StampedLock, Long> 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<StampedLock, Long> 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() { Runnable[] actions = { () -> { StampedLock sl = new StampedLock(); long stamp = assertValid(sl, sl.tryOptimisticRead()); sl.unlockRead(stamp); }, () -> { StampedLock sl = new StampedLock(); long stamp = sl.tryOptimisticRead(); sl.unlock(stamp); }, () -> { StampedLock sl = new StampedLock(); long stamp = sl.tryOptimisticRead(); sl.writeLock(); sl.unlock(stamp); }, () -> { StampedLock sl = new StampedLock(); sl.readLock(); long stamp = assertValid(sl, sl.tryOptimisticRead()); sl.unlockRead(stamp); }, () -> { StampedLock sl = new StampedLock(); sl.readLock(); long stamp = assertValid(sl, sl.tryOptimisticRead()); sl.unlock(stamp); }, () -> { StampedLock sl = new StampedLock(); long stamp = sl.tryConvertToOptimisticRead(sl.writeLock()); assertValid(sl, stamp); sl.writeLock(); sl.unlockWrite(stamp); }, () -> { StampedLock sl = new StampedLock(); long stamp = sl.tryConvertToOptimisticRead(sl.writeLock()); sl.writeLock(); sl.unlock(stamp); }, () -> { StampedLock sl = new StampedLock(); long stamp = sl.tryConvertToOptimisticRead(sl.writeLock()); sl.readLock(); sl.unlockRead(stamp); }, () -> { StampedLock sl = new StampedLock(); long stamp = sl.tryConvertToOptimisticRead(sl.writeLock()); sl.readLock(); sl.unlock(stamp); }, () -> { StampedLock sl = new StampedLock(); long stamp = sl.tryConvertToOptimisticRead(sl.readLock()); assertValid(sl, stamp); sl.writeLock(); sl.unlockWrite(stamp); }, () -> { StampedLock sl = new StampedLock(); long stamp = sl.tryConvertToOptimisticRead(sl.readLock()); sl.writeLock(); sl.unlock(stamp); }, () -> { StampedLock sl = new StampedLock(); long stamp = sl.tryConvertToOptimisticRead(sl.readLock()); sl.readLock(); sl.unlockRead(stamp); }, () -> { StampedLock sl = new StampedLock(); sl.readLock(); long stamp = sl.tryConvertToOptimisticRead(sl.readLock()); assertValid(sl, stamp); sl.readLock(); sl.unlockRead(stamp); }, () -> { StampedLock sl = new StampedLock(); long stamp = sl.tryConvertToOptimisticRead(sl.readLock()); sl.readLock(); sl.unlock(stamp); }, () -> { StampedLock sl = new StampedLock(); sl.readLock(); long stamp = sl.tryConvertToOptimisticRead(sl.readLock()); sl.readLock(); sl.unlock(stamp); }, }; assertThrows(IllegalMonitorStateException.class, actions); } 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<StampedLock, Long> readLocker : readLockers()) for (BiConsumer<StampedLock, Long> 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<StampedLock, Long> writeLocker : writeLockers()) for (BiConsumer<StampedLock, Long> 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<Function<StampedLock, Long>> readLockers = readLockers(); final List<BiConsumer<StampedLock, Long>> readUnlockers = readUnlockers(); for (int i = 0; i < depth; i++) { Function<StampedLock, Long> 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<StampedLock, Long> readUnlocker = readUnlockers.get(i % readUnlockers.size()); assertEquals(depth - i, lock.getReadLockCount()); assertTrue(lock.isReadLocked()); readUnlocker.accept(lock, stamps[depth - 1 - i]); } assertUnlocked(lock); } }