| /* |
| * 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 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(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 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 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); |
| } |
| |
| /** |
| * 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() { |
| 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 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<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); |
| } |
| } |