8187408: AbstractQueuedSynchronizer wait queue corrupted when thread awaits without holding the lock

Reviewed-by: martin, psandoz, dholmes
This commit is contained in:
Doug Lea 2017-10-03 13:37:01 -07:00
parent dfce305868
commit c6e3667228
5 changed files with 122 additions and 37 deletions

View file

@ -194,19 +194,13 @@ import java.util.concurrent.TimeUnit;
* represent the locked state. While a non-reentrant lock
* does not strictly require recording of the current owner
* thread, this class does so anyway to make usage easier to monitor.
* It also supports conditions and exposes
* one of the instrumentation methods:
* It also supports conditions and exposes some instrumentation methods:
*
* <pre> {@code
* class Mutex implements Lock, java.io.Serializable {
*
* // Our internal helper class
* private static class Sync extends AbstractQueuedSynchronizer {
* // Reports whether in locked state
* protected boolean isHeldExclusively() {
* return getState() == 1;
* }
*
* // Acquires the lock if state is zero
* public boolean tryAcquire(int acquires) {
* assert acquires == 1; // Otherwise unused
@ -220,14 +214,27 @@ import java.util.concurrent.TimeUnit;
* // Releases the lock by setting state to zero
* protected boolean tryRelease(int releases) {
* assert releases == 1; // Otherwise unused
* if (getState() == 0) throw new IllegalMonitorStateException();
* if (!isHeldExclusively())
* throw new IllegalMonitorStateException();
* setExclusiveOwnerThread(null);
* setState(0);
* return true;
* }
*
* // Reports whether in locked state
* public boolean isLocked() {
* return getState() != 0;
* }
*
* public boolean isHeldExclusively() {
* // a data race, but safe due to out-of-thin-air guarantees
* return getExclusiveOwnerThread() == Thread.currentThread();
* }
*
* // Provides a Condition
* Condition newCondition() { return new ConditionObject(); }
* public Condition newCondition() {
* return new ConditionObject();
* }
*
* // Deserializes properly
* private void readObject(ObjectInputStream s)
@ -240,12 +247,17 @@ import java.util.concurrent.TimeUnit;
* // The sync object does all the hard work. We just forward to it.
* private final Sync sync = new Sync();
*
* public void lock() { sync.acquire(1); }
* public boolean tryLock() { return sync.tryAcquire(1); }
* public void unlock() { sync.release(1); }
* public Condition newCondition() { return sync.newCondition(); }
* public boolean isLocked() { return sync.isHeldExclusively(); }
* public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
* public void lock() { sync.acquire(1); }
* public boolean tryLock() { return sync.tryAcquire(1); }
* public void unlock() { sync.release(1); }
* public Condition newCondition() { return sync.newCondition(); }
* public boolean isLocked() { return sync.isLocked(); }
* public boolean isHeldByCurrentThread() {
* return sync.isHeldExclusively();
* }
* public boolean hasQueuedThreads() {
* return sync.hasQueuedThreads();
* }
* public void lockInterruptibly() throws InterruptedException {
* sync.acquireInterruptibly(1);
* }
@ -1193,8 +1205,7 @@ public abstract class AbstractQueuedSynchronizer
/**
* Returns {@code true} if synchronization is held exclusively with
* respect to the current (calling) thread. This method is invoked
* upon each call to a non-waiting {@link ConditionObject} method.
* (Waiting methods instead invoke {@link #release}.)
* upon each call to a {@link ConditionObject} method.
*
* <p>The default implementation throws {@link
* UnsupportedOperationException}. This method is invoked
@ -1834,9 +1845,8 @@ public abstract class AbstractQueuedSynchronizer
}
/**
* Condition implementation for a {@link
* AbstractQueuedSynchronizer} serving as the basis of a {@link
* Lock} implementation.
* Condition implementation for a {@link AbstractQueuedSynchronizer}
* serving as the basis of a {@link Lock} implementation.
*
* <p>Method documentation for this class describes mechanics,
* not behavioral specifications from the point of view of Lock
@ -1867,6 +1877,8 @@ public abstract class AbstractQueuedSynchronizer
* @return its new wait node
*/
private Node addConditionWaiter() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {