81e9e6a7fc
Reviewed-by: dl
1531 lines
56 KiB
Java
1531 lines
56 KiB
Java
/*
|
|
* 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.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<Action> 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<Function<StampedLock, Long>> 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<BiConsumer<StampedLock, Long>> 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<Function<StampedLock, Long>> 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<BiConsumer<StampedLock, Long>> 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<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 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<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() {
|
|
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<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"));
|
|
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<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() {
|
|
{
|
|
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<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);
|
|
}
|
|
|
|
/**
|
|
* 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<Future<?>> futures = new ArrayList<>();
|
|
for (Function<StampedLock, Long> writeLocker : writeLockers())
|
|
for (BiConsumer<StampedLock, Long> 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<CompletableFuture<?>> futures = new ArrayList<>();
|
|
final List<Callable<Long>> 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<Callable<Long>> 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<Consumer<Long>> stampedWriteUnlockers = List.of(
|
|
stamp -> sl.unlockWrite(stamp),
|
|
stamp -> sl.unlock(stamp),
|
|
stamp -> assertTrue(sl.tryUnlockWrite()),
|
|
stamp -> wl.unlock(),
|
|
stamp -> sl.tryConvertToOptimisticRead(stamp));
|
|
final List<Consumer<Long>> 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);
|
|
}
|
|
|
|
}
|