# HG changeset patch # User dl # Date 1568484653 25200 # Sat Sep 14 11:10:53 2019 -0700 # Node ID 6e1ff746320c49ba09d2164ba39dde4b8fc2b084 # Parent 515fc9f6b2d6138287fe4cfa7487803f2610f34c 8229442: AQS and lock classes refresh Reviewed-by: martin diff --git a/src/java.base/share/classes/java/util/concurrent/locks/AbstractQueuedLongSynchronizer.java b/src/java.base/share/classes/java/util/concurrent/locks/AbstractQueuedLongSynchronizer.java --- a/src/java.base/share/classes/java/util/concurrent/locks/AbstractQueuedLongSynchronizer.java +++ b/src/java.base/share/classes/java/util/concurrent/locks/AbstractQueuedLongSynchronizer.java @@ -35,13 +35,12 @@ package java.util.concurrent.locks; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.VarHandle; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.AbstractQueuedSynchronizer.Node; +import java.util.concurrent.ForkJoinPool; +import jdk.internal.misc.Unsafe; /** * A version of {@link AbstractQueuedSynchronizer} in @@ -73,23 +72,76 @@ * keep it that way. */ - /** - * Creates a new {@code AbstractQueuedLongSynchronizer} instance - * with initial synchronization state of zero. - */ - protected AbstractQueuedLongSynchronizer() { } + // Node status bits, also used as argument and return values + static final int WAITING = 1; // must be 1 + static final int CANCELLED = 0x80000000; // must be negative + static final int COND = 2; // in a condition wait + + /** CLH Nodes */ + abstract static class Node { + volatile Node prev; // initially attached via casTail + volatile Node next; // visibly nonnull when signallable + Thread waiter; // visibly nonnull when enqueued + volatile int status; // written by owner, atomic bit ops by others + + // methods for atomic operations + final boolean casPrev(Node c, Node v) { // for cleanQueue + return U.weakCompareAndSetReference(this, PREV, c, v); + } + final boolean casNext(Node c, Node v) { // for cleanQueue + return U.weakCompareAndSetReference(this, NEXT, c, v); + } + final int getAndUnsetStatus(int v) { // for signalling + return U.getAndBitwiseAndInt(this, STATUS, ~v); + } + final void setPrevRelaxed(Node p) { // for off-queue assignment + U.putReference(this, PREV, p); + } + final void setStatusRelaxed(int s) { // for off-queue assignment + U.putInt(this, STATUS, s); + } + final void clearStatus() { // for reducing unneeded signals + U.putIntOpaque(this, STATUS, 0); + } + + private static final long STATUS + = U.objectFieldOffset(Node.class, "status"); + private static final long NEXT + = U.objectFieldOffset(Node.class, "next"); + private static final long PREV + = U.objectFieldOffset(Node.class, "prev"); + } + + // Concrete classes tagged by type + static final class ExclusiveNode extends Node { } + static final class SharedNode extends Node { } + + static final class ConditionNode extends Node + implements ForkJoinPool.ManagedBlocker { + ConditionNode nextWaiter; // link to next waiting node + + /** + * Allows Conditions to be used in ForkJoinPools without + * risking fixed pool exhaustion. This is usable only for + * untimed Condition waits, not timed versions. + */ + public final boolean isReleasable() { + return status <= 1 || Thread.currentThread().isInterrupted(); + } + + public final boolean block() { + while (!isReleasable()) LockSupport.park(this); + return true; + } + } /** - * Head of the wait queue, lazily initialized. Except for - * initialization, it is modified only via method setHead. Note: - * If head exists, its waitStatus is guaranteed not to be - * CANCELLED. + * Head of the wait queue, lazily initialized. */ private transient volatile Node head; /** - * Tail of the wait queue, lazily initialized. Modified only via - * method enq to add new wait node. + * Tail of the wait queue. After initialization, modified only via casTail. */ private transient volatile Node tail; @@ -113,8 +165,7 @@ * @param newState the new state value */ protected final void setState(long newState) { - // See JDK-8180620: Clarify VarHandle mixed-access subtleties - STATE.setVolatile(this, newState); + state = newState; } /** @@ -129,481 +180,234 @@ * value was not equal to the expected value. */ protected final boolean compareAndSetState(long expect, long update) { - return STATE.compareAndSet(this, expect, update); + return U.compareAndSetLong(this, STATE, expect, update); } // Queuing utilities - /** - * The number of nanoseconds for which it is faster to spin - * rather than to use timed park. A rough estimate suffices - * to improve responsiveness with very short timeouts. - */ - static final long SPIN_FOR_TIMEOUT_THRESHOLD = 1000L; + private boolean casTail(Node c, Node v) { + return U.compareAndSetReference(this, TAIL, c, v); + } + + /** tries once to CAS a new dummy node for head */ + private void tryInitializeHead() { + Node h = new ExclusiveNode(); + if (U.compareAndSetReference(this, HEAD, null, h)) + tail = h; + } /** - * Inserts node into queue, initializing if necessary. See picture above. - * @param node the node to insert - * @return node's predecessor + * Enqueues the node unless null. (Currently used only for + * ConditionNodes; other cases are interleaved with acquires.) */ - private Node enq(Node node) { - for (;;) { - Node oldTail = tail; - if (oldTail != null) { - node.setPrevRelaxed(oldTail); - if (compareAndSetTail(oldTail, node)) { - oldTail.next = node; - return oldTail; + final void enqueue(Node node) { + if (node != null) { + for (;;) { + Node t = tail; + node.setPrevRelaxed(t); // avoid unnecessary fence + if (t == null) // initialize + tryInitializeHead(); + else if (casTail(t, node)) { + t.next = node; + if (t.status < 0) // wake up to clean link + LockSupport.unpark(node.waiter); + break; } - } else { - initializeSyncQueue(); } } } + /** Returns true if node is found in traversal from tail */ + final boolean isEnqueued(Node node) { + for (Node t = tail; t != null; t = t.prev) + if (t == node) + return true; + return false; + } + /** - * Creates and enqueues node for current thread and given mode. + * Wakes up the successor of given node, if one exists, and unsets its + * WAITING status to avoid park race. This may fail to wake up an + * eligible thread when one or more have been cancelled, but + * cancelAcquire ensures liveness. + */ + private static void signalNext(Node h) { + Node s; + if (h != null && (s = h.next) != null && s.status != 0) { + s.getAndUnsetStatus(WAITING); + LockSupport.unpark(s.waiter); + } + } + + /** Wakes up the given node if in shared mode */ + private static void signalNextIfShared(Node h) { + Node s; + if (h != null && (s = h.next) != null && + (s instanceof SharedNode) && s.status != 0) { + s.getAndUnsetStatus(WAITING); + LockSupport.unpark(s.waiter); + } + } + + /** + * Main acquire method, invoked by all exported acquire methods. * - * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared - * @return the new node + * @param node null unless a reacquiring Condition + * @param arg the acquire argument + * @param shared true if shared mode else exclusive + * @param interruptible if abort and return negative on interrupt + * @param timed if true use timed waits + * @param time if timed, the System.nanoTime value to timeout + * @return positive if acquired, 0 if timed out, negative if interrupted */ - private Node addWaiter(Node mode) { - Node node = new Node(mode); + final int acquire(Node node, long arg, boolean shared, + boolean interruptible, boolean timed, long time) { + Thread current = Thread.currentThread(); + byte spins = 0, postSpins = 0; // retries upon unpark of first thread + boolean interrupted = false, first = false; + Node pred = null; // predecessor of node when enqueued + + /* + * Repeatedly: + * Check if node now first + * if so, ensure head stable, else ensure valid predecessor + * if node is first or not yet enqueued, try acquiring + * else if node not yet created, create it + * else if not yet enqueued, try once to enqueue + * else if woken from park, retry (up to postSpins times) + * else if WAITING status not set, set and retry + * else park and clear WAITING status, and check cancellation + */ for (;;) { - Node oldTail = tail; - if (oldTail != null) { - node.setPrevRelaxed(oldTail); - if (compareAndSetTail(oldTail, node)) { - oldTail.next = node; - return node; + if (!first && (pred = (node == null) ? null : node.prev) != null && + !(first = (head == pred))) { + if (pred.status < 0) { + cleanQueue(); // predecessor cancelled + continue; + } else if (pred.prev == null) { + Thread.onSpinWait(); // ensure serialization + continue; } + } + if (first || pred == null) { + boolean acquired; + try { + if (shared) + acquired = (tryAcquireShared(arg) >= 0); + else + acquired = tryAcquire(arg); + } catch (Throwable ex) { + cancelAcquire(node, interrupted, false); + throw ex; + } + if (acquired) { + if (first) { + node.prev = null; + head = node; + pred.next = null; + node.waiter = null; + if (shared) + signalNextIfShared(node); + if (interrupted) + current.interrupt(); + } + return 1; + } + } + if (node == null) { // allocate; retry before enqueue + if (shared) + node = new SharedNode(); + else + node = new ExclusiveNode(); + } else if (pred == null) { // try to enqueue + node.waiter = current; + Node t = tail; + node.setPrevRelaxed(t); // avoid unnecessary fence + if (t == null) + tryInitializeHead(); + else if (!casTail(t, node)) + node.setPrevRelaxed(null); // back out + else + t.next = node; + } else if (first && spins != 0) { + --spins; // reduce unfairness on rewaits + Thread.onSpinWait(); + } else if (node.status == 0) { + node.status = WAITING; // enable signal and recheck } else { - initializeSyncQueue(); + long nanos; + spins = postSpins = (byte)((postSpins << 1) | 1); + if (!timed) + LockSupport.park(this); + else if ((nanos = time - System.nanoTime()) > 0L) + LockSupport.parkNanos(this, nanos); + else + break; + node.clearStatus(); + if ((interrupted |= Thread.interrupted()) && interruptible) + break; + } + } + return cancelAcquire(node, interrupted, interruptible); + } + + /** + * Possibly repeatedly traverses from tail, unsplicing cancelled + * nodes until none are found. + */ + private void cleanQueue() { + for (;;) { // restart point + for (Node q = tail, s = null, p, n;;) { // (p, q, s) triples + if (q == null || (p = q.prev) == null) + return; // end of list + if (s == null ? tail != q : (s.prev != q || s.status < 0)) + break; // inconsistent + if (q.status < 0) { // cancelled + if ((s == null ? casTail(q, p) : s.casPrev(q, p)) && + q.prev == p) { + p.casNext(q, s); // OK if fails + if (p.prev == null) + signalNext(p); + } + break; + } + if ((n = p.next) != q) { // help finish + if (n != null && q.prev == p) { + p.casNext(n, q); + if (p.prev == null) + signalNext(p); + } + break; + } + s = q; + q = q.prev; } } } /** - * Sets head of queue to be node, thus dequeuing. Called only by - * acquire methods. Also nulls out unused fields for sake of GC - * and to suppress unnecessary signals and traversals. - * - * @param node the node - */ - private void setHead(Node node) { - head = node; - node.thread = null; - node.prev = null; - } - - /** - * Wakes up node's successor, if one exists. - * - * @param node the node - */ - private void unparkSuccessor(Node node) { - /* - * If status is negative (i.e., possibly needing signal) try - * to clear in anticipation of signalling. It is OK if this - * fails or if status is changed by waiting thread. - */ - int ws = node.waitStatus; - if (ws < 0) - node.compareAndSetWaitStatus(ws, 0); - - /* - * Thread to unpark is held in successor, which is normally - * just the next node. But if cancelled or apparently null, - * traverse backwards from tail to find the actual - * non-cancelled successor. - */ - Node s = node.next; - if (s == null || s.waitStatus > 0) { - s = null; - for (Node p = tail; p != node && p != null; p = p.prev) - if (p.waitStatus <= 0) - s = p; - } - if (s != null) - LockSupport.unpark(s.thread); - } - - /** - * Release action for shared mode -- signals successor and ensures - * propagation. (Note: For exclusive mode, release just amounts - * to calling unparkSuccessor of head if it needs signal.) - */ - private void doReleaseShared() { - /* - * Ensure that a release propagates, even if there are other - * in-progress acquires/releases. This proceeds in the usual - * way of trying to unparkSuccessor of head if it needs - * signal. But if it does not, status is set to PROPAGATE to - * ensure that upon release, propagation continues. - * Additionally, we must loop in case a new node is added - * while we are doing this. Also, unlike other uses of - * unparkSuccessor, we need to know if CAS to reset status - * fails, if so rechecking. - */ - for (;;) { - Node h = head; - if (h != null && h != tail) { - int ws = h.waitStatus; - if (ws == Node.SIGNAL) { - if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0)) - continue; // loop to recheck cases - unparkSuccessor(h); - } - else if (ws == 0 && - !h.compareAndSetWaitStatus(0, Node.PROPAGATE)) - continue; // loop on failed CAS - } - if (h == head) // loop if head changed - break; - } - } - - /** - * Sets head of queue, and checks if successor may be waiting - * in shared mode, if so propagating if either propagate > 0 or - * PROPAGATE status was set. - * - * @param node the node - * @param propagate the return value from a tryAcquireShared - */ - private void setHeadAndPropagate(Node node, long propagate) { - Node h = head; // Record old head for check below - setHead(node); - /* - * Try to signal next queued node if: - * Propagation was indicated by caller, - * or was recorded (as h.waitStatus either before - * or after setHead) by a previous operation - * (note: this uses sign-check of waitStatus because - * PROPAGATE status may transition to SIGNAL.) - * and - * The next node is waiting in shared mode, - * or we don't know, because it appears null - * - * The conservatism in both of these checks may cause - * unnecessary wake-ups, but only when there are multiple - * racing acquires/releases, so most need signals now or soon - * anyway. - */ - if (propagate > 0 || h == null || h.waitStatus < 0 || - (h = head) == null || h.waitStatus < 0) { - Node s = node.next; - if (s == null || s.isShared()) - doReleaseShared(); - } - } - - // Utilities for various versions of acquire - - /** * Cancels an ongoing attempt to acquire. * - * @param node the node - */ - private void cancelAcquire(Node node) { - // Ignore if node doesn't exist - if (node == null) - return; - - node.thread = null; - - // Skip cancelled predecessors - Node pred = node.prev; - while (pred.waitStatus > 0) - node.prev = pred = pred.prev; - - // predNext is the apparent node to unsplice. CASes below will - // fail if not, in which case, we lost race vs another cancel - // or signal, so no further action is necessary, although with - // a possibility that a cancelled node may transiently remain - // reachable. - Node predNext = pred.next; - - // Can use unconditional write instead of CAS here. - // After this atomic step, other Nodes can skip past us. - // Before, we are free of interference from other threads. - node.waitStatus = Node.CANCELLED; - - // If we are the tail, remove ourselves. - if (node == tail && compareAndSetTail(node, pred)) { - pred.compareAndSetNext(predNext, null); - } else { - // If successor needs signal, try to set pred's next-link - // so it will get one. Otherwise wake it up to propagate. - int ws; - if (pred != head && - ((ws = pred.waitStatus) == Node.SIGNAL || - (ws <= 0 && pred.compareAndSetWaitStatus(ws, Node.SIGNAL))) && - pred.thread != null) { - Node next = node.next; - if (next != null && next.waitStatus <= 0) - pred.compareAndSetNext(predNext, next); - } else { - unparkSuccessor(node); - } - - node.next = node; // help GC - } - } - - /** - * Checks and updates status for a node that failed to acquire. - * Returns true if thread should block. This is the main signal - * control in all acquire loops. Requires that pred == node.prev. - * - * @param pred node's predecessor holding status - * @param node the node - * @return {@code true} if thread should block - */ - private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { - int ws = pred.waitStatus; - if (ws == Node.SIGNAL) - /* - * This node has already set status asking a release - * to signal it, so it can safely park. - */ - return true; - if (ws > 0) { - /* - * Predecessor was cancelled. Skip over predecessors and - * indicate retry. - */ - do { - node.prev = pred = pred.prev; - } while (pred.waitStatus > 0); - pred.next = node; - } else { - /* - * waitStatus must be 0 or PROPAGATE. Indicate that we - * need a signal, but don't park yet. Caller will need to - * retry to make sure it cannot acquire before parking. - */ - pred.compareAndSetWaitStatus(ws, Node.SIGNAL); - } - return false; - } - - /** - * Convenience method to interrupt current thread. - */ - static void selfInterrupt() { - Thread.currentThread().interrupt(); - } - - /** - * Convenience method to park and then check if interrupted. - * - * @return {@code true} if interrupted - */ - private final boolean parkAndCheckInterrupt() { - LockSupport.park(this); - return Thread.interrupted(); - } - - /* - * Various flavors of acquire, varying in exclusive/shared and - * control modes. Each is mostly the same, but annoyingly - * different. Only a little bit of factoring is possible due to - * interactions of exception mechanics (including ensuring that we - * cancel if tryAcquire throws exception) and other control, at - * least not without hurting performance too much. - */ - - /** - * Acquires in exclusive uninterruptible mode for thread already in - * queue. Used by condition wait methods as well as acquire. - * - * @param node the node - * @param arg the acquire argument - * @return {@code true} if interrupted while waiting - */ - final boolean acquireQueued(final Node node, long arg) { - boolean interrupted = false; - try { - for (;;) { - final Node p = node.predecessor(); - if (p == head && tryAcquire(arg)) { - setHead(node); - p.next = null; // help GC - return interrupted; - } - if (shouldParkAfterFailedAcquire(p, node)) - interrupted |= parkAndCheckInterrupt(); - } - } catch (Throwable t) { - cancelAcquire(node); - if (interrupted) - selfInterrupt(); - throw t; - } - } - - /** - * Acquires in exclusive interruptible mode. - * @param arg the acquire argument + * @param node the node (may be null if cancelled before enqueuing) + * @param interrupted true if thread interrupted + * @param interruptible if should report interruption vs reset */ - private void doAcquireInterruptibly(long arg) - throws InterruptedException { - final Node node = addWaiter(Node.EXCLUSIVE); - try { - for (;;) { - final Node p = node.predecessor(); - if (p == head && tryAcquire(arg)) { - setHead(node); - p.next = null; // help GC - return; - } - if (shouldParkAfterFailedAcquire(p, node) && - parkAndCheckInterrupt()) - throw new InterruptedException(); - } - } catch (Throwable t) { - cancelAcquire(node); - throw t; - } - } - - /** - * Acquires in exclusive timed mode. - * - * @param arg the acquire argument - * @param nanosTimeout max wait time - * @return {@code true} if acquired - */ - private boolean doAcquireNanos(long arg, long nanosTimeout) - throws InterruptedException { - if (nanosTimeout <= 0L) - return false; - final long deadline = System.nanoTime() + nanosTimeout; - final Node node = addWaiter(Node.EXCLUSIVE); - try { - for (;;) { - final Node p = node.predecessor(); - if (p == head && tryAcquire(arg)) { - setHead(node); - p.next = null; // help GC - return true; - } - nanosTimeout = deadline - System.nanoTime(); - if (nanosTimeout <= 0L) { - cancelAcquire(node); - return false; - } - if (shouldParkAfterFailedAcquire(p, node) && - nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD) - LockSupport.parkNanos(this, nanosTimeout); - if (Thread.interrupted()) - throw new InterruptedException(); - } - } catch (Throwable t) { - cancelAcquire(node); - throw t; + private int cancelAcquire(Node node, boolean interrupted, + boolean interruptible) { + if (node != null) { + node.waiter = null; + node.status = CANCELLED; + if (node.prev != null) + cleanQueue(); } - } - - /** - * Acquires in shared uninterruptible mode. - * @param arg the acquire argument - */ - private void doAcquireShared(long arg) { - final Node node = addWaiter(Node.SHARED); - boolean interrupted = false; - try { - for (;;) { - final Node p = node.predecessor(); - if (p == head) { - long r = tryAcquireShared(arg); - if (r >= 0) { - setHeadAndPropagate(node, r); - p.next = null; // help GC - return; - } - } - if (shouldParkAfterFailedAcquire(p, node)) - interrupted |= parkAndCheckInterrupt(); - } - } catch (Throwable t) { - cancelAcquire(node); - throw t; - } finally { - if (interrupted) - selfInterrupt(); + if (interrupted) { + if (interruptible) + return CANCELLED; + else + Thread.currentThread().interrupt(); } - } - - /** - * Acquires in shared interruptible mode. - * @param arg the acquire argument - */ - private void doAcquireSharedInterruptibly(long arg) - throws InterruptedException { - final Node node = addWaiter(Node.SHARED); - try { - for (;;) { - final Node p = node.predecessor(); - if (p == head) { - long r = tryAcquireShared(arg); - if (r >= 0) { - setHeadAndPropagate(node, r); - p.next = null; // help GC - return; - } - } - if (shouldParkAfterFailedAcquire(p, node) && - parkAndCheckInterrupt()) - throw new InterruptedException(); - } - } catch (Throwable t) { - cancelAcquire(node); - throw t; - } - } - - /** - * Acquires in shared timed mode. - * - * @param arg the acquire argument - * @param nanosTimeout max wait time - * @return {@code true} if acquired - */ - private boolean doAcquireSharedNanos(long arg, long nanosTimeout) - throws InterruptedException { - if (nanosTimeout <= 0L) - return false; - final long deadline = System.nanoTime() + nanosTimeout; - final Node node = addWaiter(Node.SHARED); - try { - for (;;) { - final Node p = node.predecessor(); - if (p == head) { - long r = tryAcquireShared(arg); - if (r >= 0) { - setHeadAndPropagate(node, r); - p.next = null; // help GC - return true; - } - } - nanosTimeout = deadline - System.nanoTime(); - if (nanosTimeout <= 0L) { - cancelAcquire(node); - return false; - } - if (shouldParkAfterFailedAcquire(p, node) && - nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD) - LockSupport.parkNanos(this, nanosTimeout); - if (Thread.interrupted()) - throw new InterruptedException(); - } - } catch (Throwable t) { - cancelAcquire(node); - throw t; - } + return 0; } // Main exported methods @@ -756,9 +560,8 @@ * can represent anything you like. */ public final void acquire(long arg) { - if (!tryAcquire(arg) && - acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) - selfInterrupt(); + if (!tryAcquire(arg)) + acquire(null, arg, false, false, false, 0L); } /** @@ -776,11 +579,10 @@ * @throws InterruptedException if the current thread is interrupted */ public final void acquireInterruptibly(long arg) - throws InterruptedException { - if (Thread.interrupted()) + throws InterruptedException { + if (Thread.interrupted() || + (!tryAcquire(arg) && acquire(null, arg, false, true, false, 0L) < 0)) throw new InterruptedException(); - if (!tryAcquire(arg)) - doAcquireInterruptibly(arg); } /** @@ -801,11 +603,20 @@ * @throws InterruptedException if the current thread is interrupted */ public final boolean tryAcquireNanos(long arg, long nanosTimeout) - throws InterruptedException { - if (Thread.interrupted()) - throw new InterruptedException(); - return tryAcquire(arg) || - doAcquireNanos(arg, nanosTimeout); + throws InterruptedException { + if (!Thread.interrupted()) { + if (tryAcquire(arg)) + return true; + if (nanosTimeout <= 0L) + return false; + int stat = acquire(null, arg, false, true, true, + System.nanoTime() + nanosTimeout); + if (stat > 0) + return true; + if (stat == 0) + return false; + } + throw new InterruptedException(); } /** @@ -820,9 +631,7 @@ */ public final boolean release(long arg) { if (tryRelease(arg)) { - Node h = head; - if (h != null && h.waitStatus != 0) - unparkSuccessor(h); + signalNext(head); return true; } return false; @@ -841,7 +650,7 @@ */ public final void acquireShared(long arg) { if (tryAcquireShared(arg) < 0) - doAcquireShared(arg); + acquire(null, arg, true, false, false, 0L); } /** @@ -858,11 +667,11 @@ * @throws InterruptedException if the current thread is interrupted */ public final void acquireSharedInterruptibly(long arg) - throws InterruptedException { - if (Thread.interrupted()) + throws InterruptedException { + if (Thread.interrupted() || + (tryAcquireShared(arg) < 0 && + acquire(null, arg, true, true, false, 0L) < 0)) throw new InterruptedException(); - if (tryAcquireShared(arg) < 0) - doAcquireSharedInterruptibly(arg); } /** @@ -883,10 +692,19 @@ */ public final boolean tryAcquireSharedNanos(long arg, long nanosTimeout) throws InterruptedException { - if (Thread.interrupted()) - throw new InterruptedException(); - return tryAcquireShared(arg) >= 0 || - doAcquireSharedNanos(arg, nanosTimeout); + if (!Thread.interrupted()) { + if (tryAcquireShared(arg) >= 0) + return true; + if (nanosTimeout <= 0L) + return false; + int stat = acquire(null, arg, true, true, true, + System.nanoTime() + nanosTimeout); + if (stat > 0) + return true; + if (stat == 0) + return false; + } + throw new InterruptedException(); } /** @@ -900,7 +718,7 @@ */ public final boolean releaseShared(long arg) { if (tryReleaseShared(arg)) { - doReleaseShared(); + signalNext(head); return true; } return false; @@ -918,7 +736,7 @@ */ public final boolean hasQueuedThreads() { for (Node p = tail, h = head; p != h && p != null; p = p.prev) - if (p.waitStatus <= 0) + if (p.status >= 0) return true; return false; } @@ -948,45 +766,16 @@ * {@code null} if no threads are currently queued */ public final Thread getFirstQueuedThread() { - // handle only fast path, else relay - return (head == tail) ? null : fullGetFirstQueuedThread(); - } - - /** - * Version of getFirstQueuedThread called when fastpath fails. - */ - private Thread fullGetFirstQueuedThread() { - /* - * The first node is normally head.next. Try to get its - * thread field, ensuring consistent reads: If thread - * field is nulled out or s.prev is no longer head, then - * some other thread(s) concurrently performed setHead in - * between some of our reads. We try this twice before - * resorting to traversal. - */ - Node h, s; - Thread st; - if (((h = head) != null && (s = h.next) != null && - s.prev == head && (st = s.thread) != null) || - ((h = head) != null && (s = h.next) != null && - s.prev == head && (st = s.thread) != null)) - return st; - - /* - * Head's next field might not have been set yet, or may have - * been unset after setHead. So we must check to see if tail - * is actually first node. If not, we continue on, safely - * traversing from tail back to head to find first, - * guaranteeing termination. - */ - - Thread firstThread = null; - for (Node p = tail; p != null && p != head; p = p.prev) { - Thread t = p.thread; - if (t != null) - firstThread = t; + Thread first = null, w; Node h, s; + if ((h = head) != null && ((s = h.next) == null || + (first = s.waiter) == null || + s.prev == null)) { + // traverse from tail on stale reads + for (Node p = tail, q; p != null && (q = p.prev) != null; p = q) + if ((w = p.waiter) != null) + first = w; } - return firstThread; + return first; } /** @@ -1003,7 +792,7 @@ if (thread == null) throw new NullPointerException(); for (Node p = tail; p != null; p = p.prev) - if (p.thread == thread) + if (p.waiter == thread) return true; return false; } @@ -1019,10 +808,8 @@ */ final boolean apparentlyFirstQueuedIsExclusive() { Node h, s; - return (h = head) != null && - (s = h.next) != null && - !s.isShared() && - s.thread != null; + return (h = head) != null && (s = h.next) != null && + !(s instanceof SharedNode) && s.waiter != null; } /** @@ -1052,7 +839,7 @@ * synchronizer might look like this: * *
 {@code
-     * protected boolean tryAcquire(int arg) {
+     * protected boolean tryAcquire(long arg) {
      *   if (isHeldExclusively()) {
      *     // A reentrant acquire; increment hold count
      *     return true;
@@ -1069,19 +856,12 @@
      * @since 1.7
      */
     public final boolean hasQueuedPredecessors() {
-        Node h, s;
-        if ((h = head) != null) {
-            if ((s = h.next) == null || s.waitStatus > 0) {
-                s = null; // traverse in case of concurrent cancellation
-                for (Node p = tail; p != h && p != null; p = p.prev) {
-                    if (p.waitStatus <= 0)
-                        s = p;
-                }
-            }
-            if (s != null && s.thread != Thread.currentThread())
-                return true;
-        }
-        return false;
+        Thread first = null; Node h, s;
+        if ((h = head) != null && ((s = h.next) == null ||
+                                   (first = s.waiter) == null ||
+                                   s.prev == null))
+            first = getFirstQueuedThread(); // retry via getFirstQueuedThread
+        return first != null && first != Thread.currentThread();
     }
 
     // Instrumentation and monitoring methods
@@ -1098,7 +878,7 @@
     public final int getQueueLength() {
         int n = 0;
         for (Node p = tail; p != null; p = p.prev) {
-            if (p.thread != null)
+            if (p.waiter != null)
                 ++n;
         }
         return n;
@@ -1118,7 +898,7 @@
     public final Collection getQueuedThreads() {
         ArrayList list = new ArrayList<>();
         for (Node p = tail; p != null; p = p.prev) {
-            Thread t = p.thread;
+            Thread t = p.waiter;
             if (t != null)
                 list.add(t);
         }
@@ -1136,8 +916,8 @@
     public final Collection getExclusiveQueuedThreads() {
         ArrayList list = new ArrayList<>();
         for (Node p = tail; p != null; p = p.prev) {
-            if (!p.isShared()) {
-                Thread t = p.thread;
+            if (!(p instanceof SharedNode)) {
+                Thread t = p.waiter;
                 if (t != null)
                     list.add(t);
             }
@@ -1156,8 +936,8 @@
     public final Collection getSharedQueuedThreads() {
         ArrayList list = new ArrayList<>();
         for (Node p = tail; p != null; p = p.prev) {
-            if (p.isShared()) {
-                Thread t = p.thread;
+            if (p instanceof SharedNode) {
+                Thread t = p.waiter;
                 if (t != null)
                     list.add(t);
             }
@@ -1180,117 +960,6 @@
             + (hasQueuedThreads() ? "non" : "") + "empty queue]";
     }
 
-
-    // Internal support methods for Conditions
-
-    /**
-     * Returns true if a node, always one that was initially placed on
-     * a condition queue, is now waiting to reacquire on sync queue.
-     * @param node the node
-     * @return true if is reacquiring
-     */
-    final boolean isOnSyncQueue(Node node) {
-        if (node.waitStatus == Node.CONDITION || node.prev == null)
-            return false;
-        if (node.next != null) // If has successor, it must be on queue
-            return true;
-        /*
-         * node.prev can be non-null, but not yet on queue because
-         * the CAS to place it on queue can fail. So we have to
-         * traverse from tail to make sure it actually made it.  It
-         * will always be near the tail in calls to this method, and
-         * unless the CAS failed (which is unlikely), it will be
-         * there, so we hardly ever traverse much.
-         */
-        return findNodeFromTail(node);
-    }
-
-    /**
-     * Returns true if node is on sync queue by searching backwards from tail.
-     * Called only when needed by isOnSyncQueue.
-     * @return true if present
-     */
-    private boolean findNodeFromTail(Node node) {
-        // We check for node first, since it's likely to be at or near tail.
-        // tail is known to be non-null, so we could re-order to "save"
-        // one null check, but we leave it this way to help the VM.
-        for (Node p = tail;;) {
-            if (p == node)
-                return true;
-            if (p == null)
-                return false;
-            p = p.prev;
-        }
-    }
-
-    /**
-     * Transfers a node from a condition queue onto sync queue.
-     * Returns true if successful.
-     * @param node the node
-     * @return true if successfully transferred (else the node was
-     * cancelled before signal)
-     */
-    final boolean transferForSignal(Node node) {
-        /*
-         * If cannot change waitStatus, the node has been cancelled.
-         */
-        if (!node.compareAndSetWaitStatus(Node.CONDITION, 0))
-            return false;
-
-        /*
-         * Splice onto queue and try to set waitStatus of predecessor to
-         * indicate that thread is (probably) waiting. If cancelled or
-         * attempt to set waitStatus fails, wake up to resync (in which
-         * case the waitStatus can be transiently and harmlessly wrong).
-         */
-        Node p = enq(node);
-        int ws = p.waitStatus;
-        if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL))
-            LockSupport.unpark(node.thread);
-        return true;
-    }
-
-    /**
-     * Transfers node, if necessary, to sync queue after a cancelled wait.
-     * Returns true if thread was cancelled before being signalled.
-     *
-     * @param node the node
-     * @return true if cancelled before the node was signalled
-     */
-    final boolean transferAfterCancelledWait(Node node) {
-        if (node.compareAndSetWaitStatus(Node.CONDITION, 0)) {
-            enq(node);
-            return true;
-        }
-        /*
-         * If we lost out to a signal(), then we can't proceed
-         * until it finishes its enq().  Cancelling during an
-         * incomplete transfer is both rare and transient, so just
-         * spin.
-         */
-        while (!isOnSyncQueue(node))
-            Thread.yield();
-        return false;
-    }
-
-    /**
-     * Invokes release with current state value; returns saved state.
-     * Cancels node and throws exception on failure.
-     * @param node the condition node for this wait
-     * @return previous sync state
-     */
-    final long fullyRelease(Node node) {
-        try {
-            long savedState = getState();
-            if (release(savedState))
-                return savedState;
-            throw new IllegalMonitorStateException();
-        } catch (Throwable t) {
-            node.waitStatus = Node.CANCELLED;
-            throw t;
-        }
-    }
-
     // Instrumentation methods for conditions
 
     /**
@@ -1384,112 +1053,38 @@
      *
      * 

This class is Serializable, but all fields are transient, * so deserialized conditions have no waiters. - * - * @since 1.6 */ public class ConditionObject implements Condition, java.io.Serializable { private static final long serialVersionUID = 1173984872572414699L; /** First node of condition queue. */ - private transient Node firstWaiter; + private transient ConditionNode firstWaiter; /** Last node of condition queue. */ - private transient Node lastWaiter; + private transient ConditionNode lastWaiter; /** * Creates a new {@code ConditionObject} instance. */ public ConditionObject() { } - // Internal methods - - /** - * Adds a new waiter to wait queue. - * @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) { - unlinkCancelledWaiters(); - t = lastWaiter; - } - - Node node = new Node(Node.CONDITION); - - if (t == null) - firstWaiter = node; - else - t.nextWaiter = node; - lastWaiter = node; - return node; - } - - /** - * Removes and transfers nodes until hit non-cancelled one or - * null. Split out from signal in part to encourage compilers - * to inline the case of no waiters. - * @param first (non-null) the first node on condition queue - */ - private void doSignal(Node first) { - do { - if ( (firstWaiter = first.nextWaiter) == null) - lastWaiter = null; - first.nextWaiter = null; - } while (!transferForSignal(first) && - (first = firstWaiter) != null); - } + // Signalling methods /** - * Removes and transfers all nodes. - * @param first (non-null) the first node on condition queue + * Removes and transfers one or all waiters to sync queue. */ - private void doSignalAll(Node first) { - lastWaiter = firstWaiter = null; - do { - Node next = first.nextWaiter; - first.nextWaiter = null; - transferForSignal(first); + private void doSignal(ConditionNode first, boolean all) { + while (first != null) { + ConditionNode next = first.nextWaiter; + if ((firstWaiter = next) == null) + lastWaiter = null; + if ((first.getAndUnsetStatus(COND) & COND) != 0) { + enqueue(first); + if (!all) + break; + } first = next; - } while (first != null); - } - - /** - * Unlinks cancelled waiter nodes from condition queue. - * Called only while holding lock. This is called when - * cancellation occurred during condition wait, and upon - * insertion of a new waiter when lastWaiter is seen to have - * been cancelled. This method is needed to avoid garbage - * retention in the absence of signals. So even though it may - * require a full traversal, it comes into play only when - * timeouts or cancellations occur in the absence of - * signals. It traverses all nodes rather than stopping at a - * particular target to unlink all pointers to garbage nodes - * without requiring many re-traversals during cancellation - * storms. - */ - private void unlinkCancelledWaiters() { - Node t = firstWaiter; - Node trail = null; - while (t != null) { - Node next = t.nextWaiter; - if (t.waitStatus != Node.CONDITION) { - t.nextWaiter = null; - if (trail == null) - firstWaiter = next; - else - trail.nextWaiter = next; - if (next == null) - lastWaiter = trail; - } - else - trail = t; - t = next; } } - // public methods - /** * Moves the longest-waiting thread, if one exists, from the * wait queue for this condition to the wait queue for the @@ -1499,11 +1094,11 @@ * returns {@code false} */ public final void signal() { + ConditionNode first = firstWaiter; if (!isHeldExclusively()) throw new IllegalMonitorStateException(); - Node first = firstWaiter; if (first != null) - doSignal(first); + doSignal(first, false); } /** @@ -1514,11 +1109,72 @@ * returns {@code false} */ public final void signalAll() { + ConditionNode first = firstWaiter; if (!isHeldExclusively()) throw new IllegalMonitorStateException(); - Node first = firstWaiter; if (first != null) - doSignalAll(first); + doSignal(first, true); + } + + // Waiting methods + + /** + * Adds node to condition list and releases lock. + * + * @param node the node + * @return savedState to reacquire after wait + */ + private long enableWait(ConditionNode node) { + if (isHeldExclusively()) { + node.waiter = Thread.currentThread(); + node.setStatusRelaxed(COND | WAITING); + ConditionNode last = lastWaiter; + if (last == null) + firstWaiter = node; + else + last.nextWaiter = node; + lastWaiter = node; + long savedState = getState(); + if (release(savedState)) + return savedState; + } + node.status = CANCELLED; // lock not held or inconsistent + throw new IllegalMonitorStateException(); + } + + /** + * Returns true if a node that was initially placed on a condition + * queue is now ready to reacquire on sync queue. + * @param node the node + * @return true if is reacquiring + */ + private boolean canReacquire(ConditionNode node) { + // check links, not status to avoid enqueue race + return node != null && node.prev != null && isEnqueued(node); + } + + /** + * Unlinks the given node and other non-waiting nodes from + * condition queue unless already unlinked. + */ + private void unlinkCancelledWaiters(ConditionNode node) { + if (node == null || node.nextWaiter != null || node == lastWaiter) { + ConditionNode w = firstWaiter, trail = null; + while (w != null) { + ConditionNode next = w.nextWaiter; + if ((w.status & COND) == 0) { + w.nextWaiter = null; + if (trail == null) + firstWaiter = next; + else + trail.nextWaiter = next; + if (next == null) + lastWaiter = trail; + } else + trail = w; + w = next; + } + } } /** @@ -1533,51 +1189,27 @@ * */ public final void awaitUninterruptibly() { - Node node = addConditionWaiter(); - long savedState = fullyRelease(node); + ConditionNode node = new ConditionNode(); + long savedState = enableWait(node); + LockSupport.setCurrentBlocker(this); // for back-compatibility boolean interrupted = false; - while (!isOnSyncQueue(node)) { - LockSupport.park(this); + while (!canReacquire(node)) { if (Thread.interrupted()) interrupted = true; + else if ((node.status & COND) != 0) { + try { + ForkJoinPool.managedBlock(node); + } catch (InterruptedException ie) { + interrupted = true; + } + } else + Thread.onSpinWait(); // awoke while enqueuing } - if (acquireQueued(node, savedState) || interrupted) - selfInterrupt(); - } - - /* - * For interruptible waits, we need to track whether to throw - * InterruptedException, if interrupted while blocked on - * condition, versus reinterrupt current thread, if - * interrupted while blocked waiting to re-acquire. - */ - - /** Mode meaning to reinterrupt on exit from wait */ - private static final int REINTERRUPT = 1; - /** Mode meaning to throw InterruptedException on exit from wait */ - private static final int THROW_IE = -1; - - /** - * Checks for interrupt, returning THROW_IE if interrupted - * before signalled, REINTERRUPT if after signalled, or - * 0 if not interrupted. - */ - private int checkInterruptWhileWaiting(Node node) { - return Thread.interrupted() ? - (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : - 0; - } - - /** - * Throws InterruptedException, reinterrupts current thread, or - * does nothing, depending on mode. - */ - private void reportInterruptAfterWait(int interruptMode) - throws InterruptedException { - if (interruptMode == THROW_IE) - throw new InterruptedException(); - else if (interruptMode == REINTERRUPT) - selfInterrupt(); + LockSupport.setCurrentBlocker(null); + node.clearStatus(); + acquire(node, savedState, false, false, false, 0L); + if (interrupted) + Thread.currentThread().interrupt(); } /** @@ -1596,20 +1228,33 @@ public final void await() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); - Node node = addConditionWaiter(); - long savedState = fullyRelease(node); - int interruptMode = 0; - while (!isOnSyncQueue(node)) { - LockSupport.park(this); - if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) - break; + ConditionNode node = new ConditionNode(); + long savedState = enableWait(node); + LockSupport.setCurrentBlocker(this); // for back-compatibility + boolean interrupted = false, cancelled = false; + while (!canReacquire(node)) { + if (interrupted |= Thread.interrupted()) { + if (cancelled = (node.getAndUnsetStatus(COND) & COND) != 0) + break; // else interrupted after signal + } else if ((node.status & COND) != 0) { + try { + ForkJoinPool.managedBlock(node); + } catch (InterruptedException ie) { + interrupted = true; + } + } else + Thread.onSpinWait(); // awoke while enqueuing } - if (acquireQueued(node, savedState) && interruptMode != THROW_IE) - interruptMode = REINTERRUPT; - if (node.nextWaiter != null) // clean up if cancelled - unlinkCancelledWaiters(); - if (interruptMode != 0) - reportInterruptAfterWait(interruptMode); + LockSupport.setCurrentBlocker(null); + node.clearStatus(); + acquire(node, savedState, false, false, false, 0L); + if (interrupted) { + if (cancelled) { + unlinkCancelledWaiters(node); + throw new InterruptedException(); + } + Thread.currentThread().interrupt(); + } } /** @@ -1629,32 +1274,29 @@ throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); - // We don't check for nanosTimeout <= 0L here, to allow - // awaitNanos(0) as a way to "yield the lock". - final long deadline = System.nanoTime() + nanosTimeout; - long initialNanos = nanosTimeout; - Node node = addConditionWaiter(); - long savedState = fullyRelease(node); - int interruptMode = 0; - while (!isOnSyncQueue(node)) { - if (nanosTimeout <= 0L) { - transferAfterCancelledWait(node); - break; - } - if (nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD) - LockSupport.parkNanos(this, nanosTimeout); - if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) - break; - nanosTimeout = deadline - System.nanoTime(); + ConditionNode node = new ConditionNode(); + long savedState = enableWait(node); + long nanos = (nanosTimeout < 0L) ? 0L : nanosTimeout; + long deadline = System.nanoTime() + nanos; + boolean cancelled = false, interrupted = false; + while (!canReacquire(node)) { + if ((interrupted |= Thread.interrupted()) || + (nanos = deadline - System.nanoTime()) <= 0L) { + if (cancelled = (node.getAndUnsetStatus(COND) & COND) != 0) + break; + } else + LockSupport.parkNanos(this, nanos); } - if (acquireQueued(node, savedState) && interruptMode != THROW_IE) - interruptMode = REINTERRUPT; - if (node.nextWaiter != null) - unlinkCancelledWaiters(); - if (interruptMode != 0) - reportInterruptAfterWait(interruptMode); + node.clearStatus(); + acquire(node, savedState, false, false, false, 0L); + if (cancelled) { + unlinkCancelledWaiters(node); + if (interrupted) + throw new InterruptedException(); + } else if (interrupted) + Thread.currentThread().interrupt(); long remaining = deadline - System.nanoTime(); // avoid overflow - return (remaining <= initialNanos) ? remaining : Long.MIN_VALUE; + return (remaining <= nanosTimeout) ? remaining : Long.MIN_VALUE; } /** @@ -1676,26 +1318,26 @@ long abstime = deadline.getTime(); if (Thread.interrupted()) throw new InterruptedException(); - Node node = addConditionWaiter(); - long savedState = fullyRelease(node); - boolean timedout = false; - int interruptMode = 0; - while (!isOnSyncQueue(node)) { - if (System.currentTimeMillis() >= abstime) { - timedout = transferAfterCancelledWait(node); - break; - } - LockSupport.parkUntil(this, abstime); - if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) - break; + ConditionNode node = new ConditionNode(); + long savedState = enableWait(node); + boolean cancelled = false, interrupted = false; + while (!canReacquire(node)) { + if ((interrupted |= Thread.interrupted()) || + System.currentTimeMillis() >= abstime) { + if (cancelled = (node.getAndUnsetStatus(COND) & COND) != 0) + break; + } else + LockSupport.parkUntil(this, abstime); } - if (acquireQueued(node, savedState) && interruptMode != THROW_IE) - interruptMode = REINTERRUPT; - if (node.nextWaiter != null) - unlinkCancelledWaiters(); - if (interruptMode != 0) - reportInterruptAfterWait(interruptMode); - return !timedout; + node.clearStatus(); + acquire(node, savedState, false, false, false, 0L); + if (cancelled) { + unlinkCancelledWaiters(node); + if (interrupted) + throw new InterruptedException(); + } else if (interrupted) + Thread.currentThread().interrupt(); + return !cancelled; } /** @@ -1717,31 +1359,28 @@ long nanosTimeout = unit.toNanos(time); if (Thread.interrupted()) throw new InterruptedException(); - // We don't check for nanosTimeout <= 0L here, to allow - // await(0, unit) as a way to "yield the lock". - final long deadline = System.nanoTime() + nanosTimeout; - Node node = addConditionWaiter(); - long savedState = fullyRelease(node); - boolean timedout = false; - int interruptMode = 0; - while (!isOnSyncQueue(node)) { - if (nanosTimeout <= 0L) { - timedout = transferAfterCancelledWait(node); - break; - } - if (nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD) - LockSupport.parkNanos(this, nanosTimeout); - if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) - break; - nanosTimeout = deadline - System.nanoTime(); + ConditionNode node = new ConditionNode(); + long savedState = enableWait(node); + long nanos = (nanosTimeout < 0L) ? 0L : nanosTimeout; + long deadline = System.nanoTime() + nanos; + boolean cancelled = false, interrupted = false; + while (!canReacquire(node)) { + if ((interrupted |= Thread.interrupted()) || + (nanos = deadline - System.nanoTime()) <= 0L) { + if (cancelled = (node.getAndUnsetStatus(COND) & COND) != 0) + break; + } else + LockSupport.parkNanos(this, nanos); } - if (acquireQueued(node, savedState) && interruptMode != THROW_IE) - interruptMode = REINTERRUPT; - if (node.nextWaiter != null) - unlinkCancelledWaiters(); - if (interruptMode != 0) - reportInterruptAfterWait(interruptMode); - return !timedout; + node.clearStatus(); + acquire(node, savedState, false, false, false, 0L); + if (cancelled) { + unlinkCancelledWaiters(node); + if (interrupted) + throw new InterruptedException(); + } else if (interrupted) + Thread.currentThread().interrupt(); + return !cancelled; } // support for instrumentation @@ -1767,8 +1406,8 @@ protected final boolean hasWaiters() { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); - for (Node w = firstWaiter; w != null; w = w.nextWaiter) { - if (w.waitStatus == Node.CONDITION) + for (ConditionNode w = firstWaiter; w != null; w = w.nextWaiter) { + if ((w.status & COND) != 0) return true; } return false; @@ -1787,8 +1426,8 @@ if (!isHeldExclusively()) throw new IllegalMonitorStateException(); int n = 0; - for (Node w = firstWaiter; w != null; w = w.nextWaiter) { - if (w.waitStatus == Node.CONDITION) + for (ConditionNode w = firstWaiter; w != null; w = w.nextWaiter) { + if ((w.status & COND) != 0) ++n; } return n; @@ -1807,9 +1446,9 @@ if (!isHeldExclusively()) throw new IllegalMonitorStateException(); ArrayList list = new ArrayList<>(); - for (Node w = firstWaiter; w != null; w = w.nextWaiter) { - if (w.waitStatus == Node.CONDITION) { - Thread t = w.thread; + for (ConditionNode w = firstWaiter; w != null; w = w.nextWaiter) { + if ((w.status & COND) != 0) { + Thread t = w.waiter; if (t != null) list.add(t); } @@ -1818,39 +1457,16 @@ } } - // VarHandle mechanics - private static final VarHandle STATE; - private static final VarHandle HEAD; - private static final VarHandle TAIL; + // Unsafe + private static final Unsafe U = Unsafe.getUnsafe(); + private static final long STATE + = U.objectFieldOffset(AbstractQueuedLongSynchronizer.class, "state"); + private static final long HEAD + = U.objectFieldOffset(AbstractQueuedLongSynchronizer.class, "head"); + private static final long TAIL + = U.objectFieldOffset(AbstractQueuedLongSynchronizer.class, "tail"); static { - try { - MethodHandles.Lookup l = MethodHandles.lookup(); - STATE = l.findVarHandle(AbstractQueuedLongSynchronizer.class, "state", long.class); - HEAD = l.findVarHandle(AbstractQueuedLongSynchronizer.class, "head", Node.class); - TAIL = l.findVarHandle(AbstractQueuedLongSynchronizer.class, "tail", Node.class); - } catch (ReflectiveOperationException e) { - throw new ExceptionInInitializerError(e); - } - - // Reduce the risk of rare disastrous classloading in first call to - // LockSupport.park: https://bugs.openjdk.java.net/browse/JDK-8074773 Class ensureLoaded = LockSupport.class; } - - /** - * Initializes head and tail fields on first contention. - */ - private final void initializeSyncQueue() { - Node h; - if (HEAD.compareAndSet(this, null, (h = new Node()))) - tail = h; - } - - /** - * CASes tail field. - */ - private final boolean compareAndSetTail(Node expect, Node update) { - return TAIL.compareAndSet(this, expect, update); - } } diff --git a/src/java.base/share/classes/java/util/concurrent/locks/AbstractQueuedSynchronizer.java b/src/java.base/share/classes/java/util/concurrent/locks/AbstractQueuedSynchronizer.java --- a/src/java.base/share/classes/java/util/concurrent/locks/AbstractQueuedSynchronizer.java +++ b/src/java.base/share/classes/java/util/concurrent/locks/AbstractQueuedSynchronizer.java @@ -35,12 +35,12 @@ package java.util.concurrent.locks; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.VarHandle; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.concurrent.TimeUnit; +import java.util.concurrent.ForkJoinPool; +import jdk.internal.misc.Unsafe; /** * Provides a framework for implementing blocking locks and related @@ -312,265 +312,208 @@ */ protected AbstractQueuedSynchronizer() { } - /** - * Wait queue node class. - * - *

The wait queue is a variant of a "CLH" (Craig, Landin, and - * Hagersten) lock queue. CLH locks are normally used for - * spinlocks. We instead use them for blocking synchronizers, but - * use the same basic tactic of holding some of the control - * information about a thread in the predecessor of its node. A - * "status" field in each node keeps track of whether a thread - * should block. A node is signalled when its predecessor - * releases. Each node of the queue otherwise serves as a - * specific-notification-style monitor holding a single waiting - * thread. The status field does NOT control whether threads are - * granted locks etc though. A thread may try to acquire if it is - * first in the queue. But being first does not guarantee success; - * it only gives the right to contend. So the currently released - * contender thread may need to rewait. - * - *

To enqueue into a CLH lock, you atomically splice it in as new - * tail. To dequeue, you just set the head field. - *

-     *      +------+  prev +-----+       +-----+
-     * head |      | <---- |     | <---- |     |  tail
-     *      +------+       +-----+       +-----+
-     * 
+ /* + * Overview. * - *

Insertion into a CLH queue requires only a single atomic - * operation on "tail", so there is a simple atomic point of - * demarcation from unqueued to queued. Similarly, dequeuing - * involves only updating the "head". However, it takes a bit - * more work for nodes to determine who their successors are, - * in part to deal with possible cancellation due to timeouts - * and interrupts. - * - *

The "prev" links (not used in original CLH locks), are mainly - * needed to handle cancellation. If a node is cancelled, its - * successor is (normally) relinked to a non-cancelled - * predecessor. For explanation of similar mechanics in the case - * of spin locks, see the papers by Scott and Scherer at - * http://www.cs.rochester.edu/u/scott/synchronization/ + * The wait queue is a variant of a "CLH" (Craig, Landin, and + * Hagersten) lock queue. CLH locks are normally used for + * spinlocks. We instead use them for blocking synchronizers by + * including explicit ("prev" and "next") links plus a "status" + * field that allow nodes to signal successors when releasing + * locks, and handle cancellation due to interrupts and timeouts. + * The status field includes bits that track whether a thread + * needs a signal (using LockSupport.unpark). Despite these + * additions, we maintain most CLH locality properties. * - *

We also use "next" links to implement blocking mechanics. - * The thread id for each node is kept in its own node, so a - * predecessor signals the next node to wake up by traversing - * next link to determine which thread it is. Determination of - * successor must avoid races with newly queued nodes to set - * the "next" fields of their predecessors. This is solved - * when necessary by checking backwards from the atomically - * updated "tail" when a node's successor appears to be null. - * (Or, said differently, the next-links are an optimization - * so that we don't usually need a backward scan.) + * To enqueue into a CLH lock, you atomically splice it in as new + * tail. To dequeue, you set the head field, so the next eligible + * waiter becomes first. * - *

Cancellation introduces some conservatism to the basic - * algorithms. Since we must poll for cancellation of other - * nodes, we can miss noticing whether a cancelled node is - * ahead or behind us. This is dealt with by always unparking - * successors upon cancellation, allowing them to stabilize on - * a new predecessor, unless we can identify an uncancelled - * predecessor who will carry this responsibility. + * +------+ prev +-------+ +------+ + * | head | <---- | first | <---- | tail | + * +------+ +-------+ +------+ * - *

CLH queues need a dummy header node to get started. But + * Insertion into a CLH queue requires only a single atomic + * operation on "tail", so there is a simple point of demarcation + * from unqueued to queued. The "next" link of the predecessor is + * set by the enqueuing thread after successful CAS. Even though + * non-atomic, this suffices to ensure that any blocked thread is + * signalled by a predecessor when eligible (although in the case + * of cancellation, possibly with the assistance of a signal in + * method cleanQueue). Signalling is based in part on a + * Dekker-like scheme in which the to-be waiting thread indicates + * WAITING status, then retries acquiring, and then rechecks + * status before blocking. The signaller atomically clears WAITING + * status when unparking. + * + * Dequeuing on acquire involves detaching (nulling) a node's + * "prev" node and then updating the "head". Other threads check + * if a node is or was dequeued by checking "prev" rather than + * head. We enforce the nulling then setting order by spin-waiting + * if necessary. Because of this, the lock algorithm is not itself + * strictly "lock-free" because an acquiring thread may need to + * wait for a previous acquire to make progress. When used with + * exclusive locks, such progress is required anyway. However + * Shared mode may (uncommonly) require a spin-wait before + * setting head field to ensure proper propagation. (Historical + * note: This allows some simplifications and efficiencies + * compared to previous versions of this class.) + * + * A node's predecessor can change due to cancellation while it is + * waiting, until the node is first in queue, at which point it + * cannot change. The acquire methods cope with this by rechecking + * "prev" before waiting. The prev and next fields are modified + * only via CAS by cancelled nodes in method cleanQueue. The + * unsplice strategy is reminiscent of Michael-Scott queues in + * that after a successful CAS to prev field, other threads help + * fix next fields. Because cancellation often occurs in bunches + * that complicate decisions about necessary signals, each call to + * cleanQueue traverses the queue until a clean sweep. Nodes that + * become relinked as first are unconditionally unparked + * (sometimes unnecessarily, but those cases are not worth + * avoiding). + * + * A thread may try to acquire if it is first (frontmost) in the + * queue, and sometimes before. Being first does not guarantee + * success; it only gives the right to contend. We balance + * throughput, overhead, and fairness by allowing incoming threads + * to "barge" and acquire the synchronizer while in the process of + * enqueuing, in which case an awakened first thread may need to + * rewait. To counteract possible repeated unlucky rewaits, we + * exponentially increase retries (up to 256) to acquire each time + * a thread is unparked. Except in this case, AQS locks do not + * spin; they instead interleave attempts to acquire with + * bookkeeping steps. (Users who want spinlocks can use + * tryAcquire.) + * + * To improve garbage collectibility, fields of nodes not yet on + * list are null. (It is not rare to create and then throw away a + * node without using it.) Fields of nodes coming off the list are + * nulled out as soon as possible. This accentuates the challenge + * of externally determining the first waiting thread (as in + * method getFirstQueuedThread). This sometimes requires the + * fallback of traversing backwards from the atomically updated + * "tail" when fields appear null. (This is never needed in the + * process of signalling though.) + * + * CLH queues need a dummy header node to get started. But * we don't create them on construction, because it would be wasted * effort if there is never contention. Instead, the node * is constructed and head and tail pointers are set upon first * contention. * - *

Threads waiting on Conditions use the same nodes, but - * use an additional link. Conditions only need to link nodes - * in simple (non-concurrent) linked queues because they are - * only accessed when exclusively held. Upon await, a node is - * inserted into a condition queue. Upon signal, the node is - * transferred to the main queue. A special value of status - * field is used to mark which queue a node is on. + * Shared mode operations differ from Exclusive in that an acquire + * signals the next waiter to try to acquire if it is also + * Shared. The tryAcquireShared API allows users to indicate the + * degree of propagation, but in most applications, it is more + * efficient to ignore this, allowing the successor to try + * acquiring in any case. * - *

Thanks go to Dave Dice, Mark Moir, Victor Luchangco, Bill + * Threads waiting on Conditions use nodes with an additional + * link to maintain the (FIFO) list of conditions. Conditions only + * need to link nodes in simple (non-concurrent) linked queues + * because they are only accessed when exclusively held. Upon + * await, a node is inserted into a condition queue. Upon signal, + * the node is enqueued on the main queue. A special status field + * value is used to track and atomically trigger this. + * + * Accesses to fields head, tail, and state use full Volatile + * mode, along with CAS. Node fields status, prev and next also do + * so while threads may be signallable, but sometimes use weaker + * modes otherwise. Accesses to field "waiter" (the thread to be + * signalled) are always sandwiched between other atomic accesses + * so are used in Plain mode. We use jdk.internal Unsafe versions + * of atomic access methods rather than VarHandles to avoid + * potential VM bootstrap issues. + * + * Most of the above is performed by primary internal method + * acquire, that is invoked in some way by all exported acquire + * methods. (It is usually easy for compilers to optimize + * call-site specializations when heavily used.) + * + * There are several arbitrary decisions about when and how to + * check interrupts in both acquire and await before and/or after + * blocking. The decisions are less arbitrary in implementation + * updates because some users appear to rely on original behaviors + * in ways that are racy and so (rarely) wrong in general but hard + * to justify changing. + * + * Thanks go to Dave Dice, Mark Moir, Victor Luchangco, Bill * Scherer and Michael Scott, along with members of JSR-166 * expert group, for helpful ideas, discussions, and critiques * on the design of this class. */ - static final class Node { - /** Marker to indicate a node is waiting in shared mode */ - static final Node SHARED = new Node(); - /** Marker to indicate a node is waiting in exclusive mode */ - static final Node EXCLUSIVE = null; - - /** waitStatus value to indicate thread has cancelled. */ - static final int CANCELLED = 1; - /** waitStatus value to indicate successor's thread needs unparking. */ - static final int SIGNAL = -1; - /** waitStatus value to indicate thread is waiting on condition. */ - static final int CONDITION = -2; - /** - * waitStatus value to indicate the next acquireShared should - * unconditionally propagate. - */ - static final int PROPAGATE = -3; - /** - * Status field, taking on only the values: - * SIGNAL: The successor of this node is (or will soon be) - * blocked (via park), so the current node must - * unpark its successor when it releases or - * cancels. To avoid races, acquire methods must - * first indicate they need a signal, - * then retry the atomic acquire, and then, - * on failure, block. - * CANCELLED: This node is cancelled due to timeout or interrupt. - * Nodes never leave this state. In particular, - * a thread with cancelled node never again blocks. - * CONDITION: This node is currently on a condition queue. - * It will not be used as a sync queue node - * until transferred, at which time the status - * will be set to 0. (Use of this value here has - * nothing to do with the other uses of the - * field, but simplifies mechanics.) - * PROPAGATE: A releaseShared should be propagated to other - * nodes. This is set (for head node only) in - * doReleaseShared to ensure propagation - * continues, even if other operations have - * since intervened. - * 0: None of the above - * - * The values are arranged numerically to simplify use. - * Non-negative values mean that a node doesn't need to - * signal. So, most code doesn't need to check for particular - * values, just for sign. - * - * The field is initialized to 0 for normal sync nodes, and - * CONDITION for condition nodes. It is modified using CAS - * (or when possible, unconditional volatile writes). - */ - volatile int waitStatus; + // Node status bits, also used as argument and return values + static final int WAITING = 1; // must be 1 + static final int CANCELLED = 0x80000000; // must be negative + static final int COND = 2; // in a condition wait - /** - * Link to predecessor node that current node/thread relies on - * for checking waitStatus. Assigned during enqueuing, and nulled - * out (for sake of GC) only upon dequeuing. Also, upon - * cancellation of a predecessor, we short-circuit while - * finding a non-cancelled one, which will always exist - * because the head node is never cancelled: A node becomes - * head only as a result of successful acquire. A - * cancelled thread never succeeds in acquiring, and a thread only - * cancels itself, not any other node. - */ - volatile Node prev; + /** CLH Nodes */ + abstract static class Node { + volatile Node prev; // initially attached via casTail + volatile Node next; // visibly nonnull when signallable + Thread waiter; // visibly nonnull when enqueued + volatile int status; // written by owner, atomic bit ops by others - /** - * Link to the successor node that the current node/thread - * unparks upon release. Assigned during enqueuing, adjusted - * when bypassing cancelled predecessors, and nulled out (for - * sake of GC) when dequeued. The enq operation does not - * assign next field of a predecessor until after attachment, - * so seeing a null next field does not necessarily mean that - * node is at end of queue. However, if a next field appears - * to be null, we can scan prev's from the tail to - * double-check. The next field of cancelled nodes is set to - * point to the node itself instead of null, to make life - * easier for isOnSyncQueue. - */ - volatile Node next; + // methods for atomic operations + final boolean casPrev(Node c, Node v) { // for cleanQueue + return U.weakCompareAndSetReference(this, PREV, c, v); + } + final boolean casNext(Node c, Node v) { // for cleanQueue + return U.weakCompareAndSetReference(this, NEXT, c, v); + } + final int getAndUnsetStatus(int v) { // for signalling + return U.getAndBitwiseAndInt(this, STATUS, ~v); + } + final void setPrevRelaxed(Node p) { // for off-queue assignment + U.putReference(this, PREV, p); + } + final void setStatusRelaxed(int s) { // for off-queue assignment + U.putInt(this, STATUS, s); + } + final void clearStatus() { // for reducing unneeded signals + U.putIntOpaque(this, STATUS, 0); + } + + private static final long STATUS + = U.objectFieldOffset(Node.class, "status"); + private static final long NEXT + = U.objectFieldOffset(Node.class, "next"); + private static final long PREV + = U.objectFieldOffset(Node.class, "prev"); + } + + // Concrete classes tagged by type + static final class ExclusiveNode extends Node { } + static final class SharedNode extends Node { } + + static final class ConditionNode extends Node + implements ForkJoinPool.ManagedBlocker { + ConditionNode nextWaiter; // link to next waiting node /** - * The thread that enqueued this node. Initialized on - * construction and nulled out after use. - */ - volatile Thread thread; - - /** - * Link to next node waiting on condition, or the special - * value SHARED. Because condition queues are accessed only - * when holding in exclusive mode, we just need a simple - * linked queue to hold nodes while they are waiting on - * conditions. They are then transferred to the queue to - * re-acquire. And because conditions can only be exclusive, - * we save a field by using special value to indicate shared - * mode. + * Allows Conditions to be used in ForkJoinPools without + * risking fixed pool exhaustion. This is usable only for + * untimed Condition waits, not timed versions. */ - Node nextWaiter; - - /** - * Returns true if node is waiting in shared mode. - */ - final boolean isShared() { - return nextWaiter == SHARED; - } - - /** - * Returns previous node, or throws NullPointerException if null. - * Use when predecessor cannot be null. The null check could - * be elided, but is present to help the VM. - * - * @return the predecessor of this node - */ - final Node predecessor() { - Node p = prev; - if (p == null) - throw new NullPointerException(); - else - return p; + public final boolean isReleasable() { + return status <= 1 || Thread.currentThread().isInterrupted(); } - /** Establishes initial head or SHARED marker. */ - Node() {} - - /** Constructor used by addWaiter. */ - Node(Node nextWaiter) { - this.nextWaiter = nextWaiter; - THREAD.set(this, Thread.currentThread()); - } - - /** Constructor used by addConditionWaiter. */ - Node(int waitStatus) { - WAITSTATUS.set(this, waitStatus); - THREAD.set(this, Thread.currentThread()); - } - - /** CASes waitStatus field. */ - final boolean compareAndSetWaitStatus(int expect, int update) { - return WAITSTATUS.compareAndSet(this, expect, update); - } - - /** CASes next field. */ - final boolean compareAndSetNext(Node expect, Node update) { - return NEXT.compareAndSet(this, expect, update); - } - - final void setPrevRelaxed(Node p) { - PREV.set(this, p); - } - - // VarHandle mechanics - private static final VarHandle NEXT; - private static final VarHandle PREV; - private static final VarHandle THREAD; - private static final VarHandle WAITSTATUS; - static { - try { - MethodHandles.Lookup l = MethodHandles.lookup(); - NEXT = l.findVarHandle(Node.class, "next", Node.class); - PREV = l.findVarHandle(Node.class, "prev", Node.class); - THREAD = l.findVarHandle(Node.class, "thread", Thread.class); - WAITSTATUS = l.findVarHandle(Node.class, "waitStatus", int.class); - } catch (ReflectiveOperationException e) { - throw new ExceptionInInitializerError(e); - } + public final boolean block() { + while (!isReleasable()) LockSupport.park(this); + return true; } } /** - * Head of the wait queue, lazily initialized. Except for - * initialization, it is modified only via method setHead. Note: - * If head exists, its waitStatus is guaranteed not to be - * CANCELLED. + * Head of the wait queue, lazily initialized. */ private transient volatile Node head; /** - * Tail of the wait queue, lazily initialized. Modified only via - * method enq to add new wait node. + * Tail of the wait queue. After initialization, modified only via casTail. */ private transient volatile Node tail; @@ -609,481 +552,235 @@ * value was not equal to the expected value. */ protected final boolean compareAndSetState(int expect, int update) { - return STATE.compareAndSet(this, expect, update); + return U.compareAndSetInt(this, STATE, expect, update); } // Queuing utilities - /** - * The number of nanoseconds for which it is faster to spin - * rather than to use timed park. A rough estimate suffices - * to improve responsiveness with very short timeouts. - */ - static final long SPIN_FOR_TIMEOUT_THRESHOLD = 1000L; + private boolean casTail(Node c, Node v) { + return U.compareAndSetReference(this, TAIL, c, v); + } + + /** tries once to CAS a new dummy node for head */ + private void tryInitializeHead() { + Node h = new ExclusiveNode(); + if (U.compareAndSetReference(this, HEAD, null, h)) + tail = h; + } /** - * Inserts node into queue, initializing if necessary. See picture above. - * @param node the node to insert - * @return node's predecessor + * Enqueues the node unless null. (Currently used only for + * ConditionNodes; other cases are interleaved with acquires.) */ - private Node enq(Node node) { - for (;;) { - Node oldTail = tail; - if (oldTail != null) { - node.setPrevRelaxed(oldTail); - if (compareAndSetTail(oldTail, node)) { - oldTail.next = node; - return oldTail; + final void enqueue(Node node) { + if (node != null) { + for (;;) { + Node t = tail; + node.setPrevRelaxed(t); // avoid unnecessary fence + if (t == null) // initialize + tryInitializeHead(); + else if (casTail(t, node)) { + t.next = node; + if (t.status < 0) // wake up to clean link + LockSupport.unpark(node.waiter); + break; } - } else { - initializeSyncQueue(); } } } + /** Returns true if node is found in traversal from tail */ + final boolean isEnqueued(Node node) { + for (Node t = tail; t != null; t = t.prev) + if (t == node) + return true; + return false; + } + /** - * Creates and enqueues node for current thread and given mode. + * Wakes up the successor of given node, if one exists, and unsets its + * WAITING status to avoid park race. This may fail to wake up an + * eligible thread when one or more have been cancelled, but + * cancelAcquire ensures liveness. + */ + private static void signalNext(Node h) { + Node s; + if (h != null && (s = h.next) != null && s.status != 0) { + s.getAndUnsetStatus(WAITING); + LockSupport.unpark(s.waiter); + } + } + + /** Wakes up the given node if in shared mode */ + private static void signalNextIfShared(Node h) { + Node s; + if (h != null && (s = h.next) != null && + (s instanceof SharedNode) && s.status != 0) { + s.getAndUnsetStatus(WAITING); + LockSupport.unpark(s.waiter); + } + } + + /** + * Main acquire method, invoked by all exported acquire methods. * - * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared - * @return the new node + * @param node null unless a reacquiring Condition + * @param arg the acquire argument + * @param shared true if shared mode else exclusive + * @param interruptible if abort and return negative on interrupt + * @param timed if true use timed waits + * @param time if timed, the System.nanoTime value to timeout + * @return positive if acquired, 0 if timed out, negative if interrupted */ - private Node addWaiter(Node mode) { - Node node = new Node(mode); + final int acquire(Node node, int arg, boolean shared, + boolean interruptible, boolean timed, long time) { + Thread current = Thread.currentThread(); + byte spins = 0, postSpins = 0; // retries upon unpark of first thread + boolean interrupted = false, first = false; + Node pred = null; // predecessor of node when enqueued + + /* + * Repeatedly: + * Check if node now first + * if so, ensure head stable, else ensure valid predecessor + * if node is first or not yet enqueued, try acquiring + * else if node not yet created, create it + * else if not yet enqueued, try once to enqueue + * else if woken from park, retry (up to postSpins times) + * else if WAITING status not set, set and retry + * else park and clear WAITING status, and check cancellation + */ for (;;) { - Node oldTail = tail; - if (oldTail != null) { - node.setPrevRelaxed(oldTail); - if (compareAndSetTail(oldTail, node)) { - oldTail.next = node; - return node; + if (!first && (pred = (node == null) ? null : node.prev) != null && + !(first = (head == pred))) { + if (pred.status < 0) { + cleanQueue(); // predecessor cancelled + continue; + } else if (pred.prev == null) { + Thread.onSpinWait(); // ensure serialization + continue; } + } + if (first || pred == null) { + boolean acquired; + try { + if (shared) + acquired = (tryAcquireShared(arg) >= 0); + else + acquired = tryAcquire(arg); + } catch (Throwable ex) { + cancelAcquire(node, interrupted, false); + throw ex; + } + if (acquired) { + if (first) { + node.prev = null; + head = node; + pred.next = null; + node.waiter = null; + if (shared) + signalNextIfShared(node); + if (interrupted) + current.interrupt(); + } + return 1; + } + } + if (node == null) { // allocate; retry before enqueue + if (shared) + node = new SharedNode(); + else + node = new ExclusiveNode(); + } else if (pred == null) { // try to enqueue + node.waiter = current; + Node t = tail; + node.setPrevRelaxed(t); // avoid unnecessary fence + if (t == null) + tryInitializeHead(); + else if (!casTail(t, node)) + node.setPrevRelaxed(null); // back out + else + t.next = node; + } else if (first && spins != 0) { + --spins; // reduce unfairness on rewaits + Thread.onSpinWait(); + } else if (node.status == 0) { + node.status = WAITING; // enable signal and recheck } else { - initializeSyncQueue(); + long nanos; + spins = postSpins = (byte)((postSpins << 1) | 1); + if (!timed) + LockSupport.park(this); + else if ((nanos = time - System.nanoTime()) > 0L) + LockSupport.parkNanos(this, nanos); + else + break; + node.clearStatus(); + if ((interrupted |= Thread.interrupted()) && interruptible) + break; + } + } + return cancelAcquire(node, interrupted, interruptible); + } + + /** + * Possibly repeatedly traverses from tail, unsplicing cancelled + * nodes until none are found. Unparks nodes that may have been + * relinked to be next eligible acquirer. + */ + private void cleanQueue() { + for (;;) { // restart point + for (Node q = tail, s = null, p, n;;) { // (p, q, s) triples + if (q == null || (p = q.prev) == null) + return; // end of list + if (s == null ? tail != q : (s.prev != q || s.status < 0)) + break; // inconsistent + if (q.status < 0) { // cancelled + if ((s == null ? casTail(q, p) : s.casPrev(q, p)) && + q.prev == p) { + p.casNext(q, s); // OK if fails + if (p.prev == null) + signalNext(p); + } + break; + } + if ((n = p.next) != q) { // help finish + if (n != null && q.prev == p) { + p.casNext(n, q); + if (p.prev == null) + signalNext(p); + } + break; + } + s = q; + q = q.prev; } } } /** - * Sets head of queue to be node, thus dequeuing. Called only by - * acquire methods. Also nulls out unused fields for sake of GC - * and to suppress unnecessary signals and traversals. - * - * @param node the node - */ - private void setHead(Node node) { - head = node; - node.thread = null; - node.prev = null; - } - - /** - * Wakes up node's successor, if one exists. - * - * @param node the node - */ - private void unparkSuccessor(Node node) { - /* - * If status is negative (i.e., possibly needing signal) try - * to clear in anticipation of signalling. It is OK if this - * fails or if status is changed by waiting thread. - */ - int ws = node.waitStatus; - if (ws < 0) - node.compareAndSetWaitStatus(ws, 0); - - /* - * Thread to unpark is held in successor, which is normally - * just the next node. But if cancelled or apparently null, - * traverse backwards from tail to find the actual - * non-cancelled successor. - */ - Node s = node.next; - if (s == null || s.waitStatus > 0) { - s = null; - for (Node p = tail; p != node && p != null; p = p.prev) - if (p.waitStatus <= 0) - s = p; - } - if (s != null) - LockSupport.unpark(s.thread); - } - - /** - * Release action for shared mode -- signals successor and ensures - * propagation. (Note: For exclusive mode, release just amounts - * to calling unparkSuccessor of head if it needs signal.) - */ - private void doReleaseShared() { - /* - * Ensure that a release propagates, even if there are other - * in-progress acquires/releases. This proceeds in the usual - * way of trying to unparkSuccessor of head if it needs - * signal. But if it does not, status is set to PROPAGATE to - * ensure that upon release, propagation continues. - * Additionally, we must loop in case a new node is added - * while we are doing this. Also, unlike other uses of - * unparkSuccessor, we need to know if CAS to reset status - * fails, if so rechecking. - */ - for (;;) { - Node h = head; - if (h != null && h != tail) { - int ws = h.waitStatus; - if (ws == Node.SIGNAL) { - if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0)) - continue; // loop to recheck cases - unparkSuccessor(h); - } - else if (ws == 0 && - !h.compareAndSetWaitStatus(0, Node.PROPAGATE)) - continue; // loop on failed CAS - } - if (h == head) // loop if head changed - break; - } - } - - /** - * Sets head of queue, and checks if successor may be waiting - * in shared mode, if so propagating if either propagate > 0 or - * PROPAGATE status was set. - * - * @param node the node - * @param propagate the return value from a tryAcquireShared - */ - private void setHeadAndPropagate(Node node, int propagate) { - Node h = head; // Record old head for check below - setHead(node); - /* - * Try to signal next queued node if: - * Propagation was indicated by caller, - * or was recorded (as h.waitStatus either before - * or after setHead) by a previous operation - * (note: this uses sign-check of waitStatus because - * PROPAGATE status may transition to SIGNAL.) - * and - * The next node is waiting in shared mode, - * or we don't know, because it appears null - * - * The conservatism in both of these checks may cause - * unnecessary wake-ups, but only when there are multiple - * racing acquires/releases, so most need signals now or soon - * anyway. - */ - if (propagate > 0 || h == null || h.waitStatus < 0 || - (h = head) == null || h.waitStatus < 0) { - Node s = node.next; - if (s == null || s.isShared()) - doReleaseShared(); - } - } - - // Utilities for various versions of acquire - - /** * Cancels an ongoing attempt to acquire. * - * @param node the node - */ - private void cancelAcquire(Node node) { - // Ignore if node doesn't exist - if (node == null) - return; - - node.thread = null; - - // Skip cancelled predecessors - Node pred = node.prev; - while (pred.waitStatus > 0) - node.prev = pred = pred.prev; - - // predNext is the apparent node to unsplice. CASes below will - // fail if not, in which case, we lost race vs another cancel - // or signal, so no further action is necessary, although with - // a possibility that a cancelled node may transiently remain - // reachable. - Node predNext = pred.next; - - // Can use unconditional write instead of CAS here. - // After this atomic step, other Nodes can skip past us. - // Before, we are free of interference from other threads. - node.waitStatus = Node.CANCELLED; - - // If we are the tail, remove ourselves. - if (node == tail && compareAndSetTail(node, pred)) { - pred.compareAndSetNext(predNext, null); - } else { - // If successor needs signal, try to set pred's next-link - // so it will get one. Otherwise wake it up to propagate. - int ws; - if (pred != head && - ((ws = pred.waitStatus) == Node.SIGNAL || - (ws <= 0 && pred.compareAndSetWaitStatus(ws, Node.SIGNAL))) && - pred.thread != null) { - Node next = node.next; - if (next != null && next.waitStatus <= 0) - pred.compareAndSetNext(predNext, next); - } else { - unparkSuccessor(node); - } - - node.next = node; // help GC - } - } - - /** - * Checks and updates status for a node that failed to acquire. - * Returns true if thread should block. This is the main signal - * control in all acquire loops. Requires that pred == node.prev. - * - * @param pred node's predecessor holding status - * @param node the node - * @return {@code true} if thread should block - */ - private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { - int ws = pred.waitStatus; - if (ws == Node.SIGNAL) - /* - * This node has already set status asking a release - * to signal it, so it can safely park. - */ - return true; - if (ws > 0) { - /* - * Predecessor was cancelled. Skip over predecessors and - * indicate retry. - */ - do { - node.prev = pred = pred.prev; - } while (pred.waitStatus > 0); - pred.next = node; - } else { - /* - * waitStatus must be 0 or PROPAGATE. Indicate that we - * need a signal, but don't park yet. Caller will need to - * retry to make sure it cannot acquire before parking. - */ - pred.compareAndSetWaitStatus(ws, Node.SIGNAL); - } - return false; - } - - /** - * Convenience method to interrupt current thread. - */ - static void selfInterrupt() { - Thread.currentThread().interrupt(); - } - - /** - * Convenience method to park and then check if interrupted. - * - * @return {@code true} if interrupted - */ - private final boolean parkAndCheckInterrupt() { - LockSupport.park(this); - return Thread.interrupted(); - } - - /* - * Various flavors of acquire, varying in exclusive/shared and - * control modes. Each is mostly the same, but annoyingly - * different. Only a little bit of factoring is possible due to - * interactions of exception mechanics (including ensuring that we - * cancel if tryAcquire throws exception) and other control, at - * least not without hurting performance too much. - */ - - /** - * Acquires in exclusive uninterruptible mode for thread already in - * queue. Used by condition wait methods as well as acquire. - * - * @param node the node - * @param arg the acquire argument - * @return {@code true} if interrupted while waiting - */ - final boolean acquireQueued(final Node node, int arg) { - boolean interrupted = false; - try { - for (;;) { - final Node p = node.predecessor(); - if (p == head && tryAcquire(arg)) { - setHead(node); - p.next = null; // help GC - return interrupted; - } - if (shouldParkAfterFailedAcquire(p, node)) - interrupted |= parkAndCheckInterrupt(); - } - } catch (Throwable t) { - cancelAcquire(node); - if (interrupted) - selfInterrupt(); - throw t; - } - } - - /** - * Acquires in exclusive interruptible mode. - * @param arg the acquire argument + * @param node the node (may be null if cancelled before enqueuing) + * @param interrupted true if thread interrupted + * @param interruptible if should report interruption vs reset */ - private void doAcquireInterruptibly(int arg) - throws InterruptedException { - final Node node = addWaiter(Node.EXCLUSIVE); - try { - for (;;) { - final Node p = node.predecessor(); - if (p == head && tryAcquire(arg)) { - setHead(node); - p.next = null; // help GC - return; - } - if (shouldParkAfterFailedAcquire(p, node) && - parkAndCheckInterrupt()) - throw new InterruptedException(); - } - } catch (Throwable t) { - cancelAcquire(node); - throw t; - } - } - - /** - * Acquires in exclusive timed mode. - * - * @param arg the acquire argument - * @param nanosTimeout max wait time - * @return {@code true} if acquired - */ - private boolean doAcquireNanos(int arg, long nanosTimeout) - throws InterruptedException { - if (nanosTimeout <= 0L) - return false; - final long deadline = System.nanoTime() + nanosTimeout; - final Node node = addWaiter(Node.EXCLUSIVE); - try { - for (;;) { - final Node p = node.predecessor(); - if (p == head && tryAcquire(arg)) { - setHead(node); - p.next = null; // help GC - return true; - } - nanosTimeout = deadline - System.nanoTime(); - if (nanosTimeout <= 0L) { - cancelAcquire(node); - return false; - } - if (shouldParkAfterFailedAcquire(p, node) && - nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD) - LockSupport.parkNanos(this, nanosTimeout); - if (Thread.interrupted()) - throw new InterruptedException(); - } - } catch (Throwable t) { - cancelAcquire(node); - throw t; + private int cancelAcquire(Node node, boolean interrupted, + boolean interruptible) { + if (node != null) { + node.waiter = null; + node.status = CANCELLED; + if (node.prev != null) + cleanQueue(); } - } - - /** - * Acquires in shared uninterruptible mode. - * @param arg the acquire argument - */ - private void doAcquireShared(int arg) { - final Node node = addWaiter(Node.SHARED); - boolean interrupted = false; - try { - for (;;) { - final Node p = node.predecessor(); - if (p == head) { - int r = tryAcquireShared(arg); - if (r >= 0) { - setHeadAndPropagate(node, r); - p.next = null; // help GC - return; - } - } - if (shouldParkAfterFailedAcquire(p, node)) - interrupted |= parkAndCheckInterrupt(); - } - } catch (Throwable t) { - cancelAcquire(node); - throw t; - } finally { - if (interrupted) - selfInterrupt(); + if (interrupted) { + if (interruptible) + return CANCELLED; + else + Thread.currentThread().interrupt(); } - } - - /** - * Acquires in shared interruptible mode. - * @param arg the acquire argument - */ - private void doAcquireSharedInterruptibly(int arg) - throws InterruptedException { - final Node node = addWaiter(Node.SHARED); - try { - for (;;) { - final Node p = node.predecessor(); - if (p == head) { - int r = tryAcquireShared(arg); - if (r >= 0) { - setHeadAndPropagate(node, r); - p.next = null; // help GC - return; - } - } - if (shouldParkAfterFailedAcquire(p, node) && - parkAndCheckInterrupt()) - throw new InterruptedException(); - } - } catch (Throwable t) { - cancelAcquire(node); - throw t; - } - } - - /** - * Acquires in shared timed mode. - * - * @param arg the acquire argument - * @param nanosTimeout max wait time - * @return {@code true} if acquired - */ - private boolean doAcquireSharedNanos(int arg, long nanosTimeout) - throws InterruptedException { - if (nanosTimeout <= 0L) - return false; - final long deadline = System.nanoTime() + nanosTimeout; - final Node node = addWaiter(Node.SHARED); - try { - for (;;) { - final Node p = node.predecessor(); - if (p == head) { - int r = tryAcquireShared(arg); - if (r >= 0) { - setHeadAndPropagate(node, r); - p.next = null; // help GC - return true; - } - } - nanosTimeout = deadline - System.nanoTime(); - if (nanosTimeout <= 0L) { - cancelAcquire(node); - return false; - } - if (shouldParkAfterFailedAcquire(p, node) && - nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD) - LockSupport.parkNanos(this, nanosTimeout); - if (Thread.interrupted()) - throw new InterruptedException(); - } - } catch (Throwable t) { - cancelAcquire(node); - throw t; - } + return 0; } // Main exported methods @@ -1236,9 +933,8 @@ * can represent anything you like. */ public final void acquire(int arg) { - if (!tryAcquire(arg) && - acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) - selfInterrupt(); + if (!tryAcquire(arg)) + acquire(null, arg, false, false, false, 0L); } /** @@ -1256,11 +952,10 @@ * @throws InterruptedException if the current thread is interrupted */ public final void acquireInterruptibly(int arg) - throws InterruptedException { - if (Thread.interrupted()) + throws InterruptedException { + if (Thread.interrupted() || + (!tryAcquire(arg) && acquire(null, arg, false, true, false, 0L) < 0)) throw new InterruptedException(); - if (!tryAcquire(arg)) - doAcquireInterruptibly(arg); } /** @@ -1281,11 +976,20 @@ * @throws InterruptedException if the current thread is interrupted */ public final boolean tryAcquireNanos(int arg, long nanosTimeout) - throws InterruptedException { - if (Thread.interrupted()) - throw new InterruptedException(); - return tryAcquire(arg) || - doAcquireNanos(arg, nanosTimeout); + throws InterruptedException { + if (!Thread.interrupted()) { + if (tryAcquire(arg)) + return true; + if (nanosTimeout <= 0L) + return false; + int stat = acquire(null, arg, false, true, true, + System.nanoTime() + nanosTimeout); + if (stat > 0) + return true; + if (stat == 0) + return false; + } + throw new InterruptedException(); } /** @@ -1300,9 +1004,7 @@ */ public final boolean release(int arg) { if (tryRelease(arg)) { - Node h = head; - if (h != null && h.waitStatus != 0) - unparkSuccessor(h); + signalNext(head); return true; } return false; @@ -1321,7 +1023,7 @@ */ public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) - doAcquireShared(arg); + acquire(null, arg, true, false, false, 0L); } /** @@ -1338,11 +1040,11 @@ * @throws InterruptedException if the current thread is interrupted */ public final void acquireSharedInterruptibly(int arg) - throws InterruptedException { - if (Thread.interrupted()) + throws InterruptedException { + if (Thread.interrupted() || + (tryAcquireShared(arg) < 0 && + acquire(null, arg, true, true, false, 0L) < 0)) throw new InterruptedException(); - if (tryAcquireShared(arg) < 0) - doAcquireSharedInterruptibly(arg); } /** @@ -1363,10 +1065,19 @@ */ public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException { - if (Thread.interrupted()) - throw new InterruptedException(); - return tryAcquireShared(arg) >= 0 || - doAcquireSharedNanos(arg, nanosTimeout); + if (!Thread.interrupted()) { + if (tryAcquireShared(arg) >= 0) + return true; + if (nanosTimeout <= 0L) + return false; + int stat = acquire(null, arg, true, true, true, + System.nanoTime() + nanosTimeout); + if (stat > 0) + return true; + if (stat == 0) + return false; + } + throw new InterruptedException(); } /** @@ -1380,7 +1091,7 @@ */ public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { - doReleaseShared(); + signalNext(head); return true; } return false; @@ -1398,7 +1109,7 @@ */ public final boolean hasQueuedThreads() { for (Node p = tail, h = head; p != h && p != null; p = p.prev) - if (p.waitStatus <= 0) + if (p.status >= 0) return true; return false; } @@ -1428,45 +1139,16 @@ * {@code null} if no threads are currently queued */ public final Thread getFirstQueuedThread() { - // handle only fast path, else relay - return (head == tail) ? null : fullGetFirstQueuedThread(); - } - - /** - * Version of getFirstQueuedThread called when fastpath fails. - */ - private Thread fullGetFirstQueuedThread() { - /* - * The first node is normally head.next. Try to get its - * thread field, ensuring consistent reads: If thread - * field is nulled out or s.prev is no longer head, then - * some other thread(s) concurrently performed setHead in - * between some of our reads. We try this twice before - * resorting to traversal. - */ - Node h, s; - Thread st; - if (((h = head) != null && (s = h.next) != null && - s.prev == head && (st = s.thread) != null) || - ((h = head) != null && (s = h.next) != null && - s.prev == head && (st = s.thread) != null)) - return st; - - /* - * Head's next field might not have been set yet, or may have - * been unset after setHead. So we must check to see if tail - * is actually first node. If not, we continue on, safely - * traversing from tail back to head to find first, - * guaranteeing termination. - */ - - Thread firstThread = null; - for (Node p = tail; p != null && p != head; p = p.prev) { - Thread t = p.thread; - if (t != null) - firstThread = t; + Thread first = null, w; Node h, s; + if ((h = head) != null && ((s = h.next) == null || + (first = s.waiter) == null || + s.prev == null)) { + // traverse from tail on stale reads + for (Node p = tail, q; p != null && (q = p.prev) != null; p = q) + if ((w = p.waiter) != null) + first = w; } - return firstThread; + return first; } /** @@ -1483,7 +1165,7 @@ if (thread == null) throw new NullPointerException(); for (Node p = tail; p != null; p = p.prev) - if (p.thread == thread) + if (p.waiter == thread) return true; return false; } @@ -1499,10 +1181,8 @@ */ final boolean apparentlyFirstQueuedIsExclusive() { Node h, s; - return (h = head) != null && - (s = h.next) != null && - !s.isShared() && - s.thread != null; + return (h = head) != null && (s = h.next) != null && + !(s instanceof SharedNode) && s.waiter != null; } /** @@ -1549,19 +1229,12 @@ * @since 1.7 */ public final boolean hasQueuedPredecessors() { - Node h, s; - if ((h = head) != null) { - if ((s = h.next) == null || s.waitStatus > 0) { - s = null; // traverse in case of concurrent cancellation - for (Node p = tail; p != h && p != null; p = p.prev) { - if (p.waitStatus <= 0) - s = p; - } - } - if (s != null && s.thread != Thread.currentThread()) - return true; - } - return false; + Thread first = null; Node h, s; + if ((h = head) != null && ((s = h.next) == null || + (first = s.waiter) == null || + s.prev == null)) + first = getFirstQueuedThread(); // retry via getFirstQueuedThread + return first != null && first != Thread.currentThread(); } // Instrumentation and monitoring methods @@ -1578,7 +1251,7 @@ public final int getQueueLength() { int n = 0; for (Node p = tail; p != null; p = p.prev) { - if (p.thread != null) + if (p.waiter != null) ++n; } return n; @@ -1598,7 +1271,7 @@ public final Collection getQueuedThreads() { ArrayList list = new ArrayList<>(); for (Node p = tail; p != null; p = p.prev) { - Thread t = p.thread; + Thread t = p.waiter; if (t != null) list.add(t); } @@ -1616,8 +1289,8 @@ public final Collection getExclusiveQueuedThreads() { ArrayList list = new ArrayList<>(); for (Node p = tail; p != null; p = p.prev) { - if (!p.isShared()) { - Thread t = p.thread; + if (!(p instanceof SharedNode)) { + Thread t = p.waiter; if (t != null) list.add(t); } @@ -1636,8 +1309,8 @@ public final Collection getSharedQueuedThreads() { ArrayList list = new ArrayList<>(); for (Node p = tail; p != null; p = p.prev) { - if (p.isShared()) { - Thread t = p.thread; + if (p instanceof SharedNode) { + Thread t = p.waiter; if (t != null) list.add(t); } @@ -1660,117 +1333,6 @@ + (hasQueuedThreads() ? "non" : "") + "empty queue]"; } - - // Internal support methods for Conditions - - /** - * Returns true if a node, always one that was initially placed on - * a condition queue, is now waiting to reacquire on sync queue. - * @param node the node - * @return true if is reacquiring - */ - final boolean isOnSyncQueue(Node node) { - if (node.waitStatus == Node.CONDITION || node.prev == null) - return false; - if (node.next != null) // If has successor, it must be on queue - return true; - /* - * node.prev can be non-null, but not yet on queue because - * the CAS to place it on queue can fail. So we have to - * traverse from tail to make sure it actually made it. It - * will always be near the tail in calls to this method, and - * unless the CAS failed (which is unlikely), it will be - * there, so we hardly ever traverse much. - */ - return findNodeFromTail(node); - } - - /** - * Returns true if node is on sync queue by searching backwards from tail. - * Called only when needed by isOnSyncQueue. - * @return true if present - */ - private boolean findNodeFromTail(Node node) { - // We check for node first, since it's likely to be at or near tail. - // tail is known to be non-null, so we could re-order to "save" - // one null check, but we leave it this way to help the VM. - for (Node p = tail;;) { - if (p == node) - return true; - if (p == null) - return false; - p = p.prev; - } - } - - /** - * Transfers a node from a condition queue onto sync queue. - * Returns true if successful. - * @param node the node - * @return true if successfully transferred (else the node was - * cancelled before signal) - */ - final boolean transferForSignal(Node node) { - /* - * If cannot change waitStatus, the node has been cancelled. - */ - if (!node.compareAndSetWaitStatus(Node.CONDITION, 0)) - return false; - - /* - * Splice onto queue and try to set waitStatus of predecessor to - * indicate that thread is (probably) waiting. If cancelled or - * attempt to set waitStatus fails, wake up to resync (in which - * case the waitStatus can be transiently and harmlessly wrong). - */ - Node p = enq(node); - int ws = p.waitStatus; - if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL)) - LockSupport.unpark(node.thread); - return true; - } - - /** - * Transfers node, if necessary, to sync queue after a cancelled wait. - * Returns true if thread was cancelled before being signalled. - * - * @param node the node - * @return true if cancelled before the node was signalled - */ - final boolean transferAfterCancelledWait(Node node) { - if (node.compareAndSetWaitStatus(Node.CONDITION, 0)) { - enq(node); - return true; - } - /* - * If we lost out to a signal(), then we can't proceed - * until it finishes its enq(). Cancelling during an - * incomplete transfer is both rare and transient, so just - * spin. - */ - while (!isOnSyncQueue(node)) - Thread.yield(); - return false; - } - - /** - * Invokes release with current state value; returns saved state. - * Cancels node and throws exception on failure. - * @param node the condition node for this wait - * @return previous sync state - */ - final int fullyRelease(Node node) { - try { - int savedState = getState(); - if (release(savedState)) - return savedState; - throw new IllegalMonitorStateException(); - } catch (Throwable t) { - node.waitStatus = Node.CANCELLED; - throw t; - } - } - // Instrumentation methods for conditions /** @@ -1868,106 +1430,34 @@ public class ConditionObject implements Condition, java.io.Serializable { private static final long serialVersionUID = 1173984872572414699L; /** First node of condition queue. */ - private transient Node firstWaiter; + private transient ConditionNode firstWaiter; /** Last node of condition queue. */ - private transient Node lastWaiter; + private transient ConditionNode lastWaiter; /** * Creates a new {@code ConditionObject} instance. */ public ConditionObject() { } - // Internal methods - - /** - * Adds a new waiter to wait queue. - * @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) { - unlinkCancelledWaiters(); - t = lastWaiter; - } - - Node node = new Node(Node.CONDITION); - - if (t == null) - firstWaiter = node; - else - t.nextWaiter = node; - lastWaiter = node; - return node; - } - - /** - * Removes and transfers nodes until hit non-cancelled one or - * null. Split out from signal in part to encourage compilers - * to inline the case of no waiters. - * @param first (non-null) the first node on condition queue - */ - private void doSignal(Node first) { - do { - if ( (firstWaiter = first.nextWaiter) == null) - lastWaiter = null; - first.nextWaiter = null; - } while (!transferForSignal(first) && - (first = firstWaiter) != null); - } + // Signalling methods /** - * Removes and transfers all nodes. - * @param first (non-null) the first node on condition queue + * Removes and transfers one or all waiters to sync queue. */ - private void doSignalAll(Node first) { - lastWaiter = firstWaiter = null; - do { - Node next = first.nextWaiter; - first.nextWaiter = null; - transferForSignal(first); + private void doSignal(ConditionNode first, boolean all) { + while (first != null) { + ConditionNode next = first.nextWaiter; + if ((firstWaiter = next) == null) + lastWaiter = null; + if ((first.getAndUnsetStatus(COND) & COND) != 0) { + enqueue(first); + if (!all) + break; + } first = next; - } while (first != null); - } - - /** - * Unlinks cancelled waiter nodes from condition queue. - * Called only while holding lock. This is called when - * cancellation occurred during condition wait, and upon - * insertion of a new waiter when lastWaiter is seen to have - * been cancelled. This method is needed to avoid garbage - * retention in the absence of signals. So even though it may - * require a full traversal, it comes into play only when - * timeouts or cancellations occur in the absence of - * signals. It traverses all nodes rather than stopping at a - * particular target to unlink all pointers to garbage nodes - * without requiring many re-traversals during cancellation - * storms. - */ - private void unlinkCancelledWaiters() { - Node t = firstWaiter; - Node trail = null; - while (t != null) { - Node next = t.nextWaiter; - if (t.waitStatus != Node.CONDITION) { - t.nextWaiter = null; - if (trail == null) - firstWaiter = next; - else - trail.nextWaiter = next; - if (next == null) - lastWaiter = trail; - } - else - trail = t; - t = next; } } - // public methods - /** * Moves the longest-waiting thread, if one exists, from the * wait queue for this condition to the wait queue for the @@ -1977,11 +1467,11 @@ * returns {@code false} */ public final void signal() { + ConditionNode first = firstWaiter; if (!isHeldExclusively()) throw new IllegalMonitorStateException(); - Node first = firstWaiter; if (first != null) - doSignal(first); + doSignal(first, false); } /** @@ -1992,11 +1482,72 @@ * returns {@code false} */ public final void signalAll() { + ConditionNode first = firstWaiter; if (!isHeldExclusively()) throw new IllegalMonitorStateException(); - Node first = firstWaiter; if (first != null) - doSignalAll(first); + doSignal(first, true); + } + + // Waiting methods + + /** + * Adds node to condition list and releases lock. + * + * @param node the node + * @return savedState to reacquire after wait + */ + private int enableWait(ConditionNode node) { + if (isHeldExclusively()) { + node.waiter = Thread.currentThread(); + node.setStatusRelaxed(COND | WAITING); + ConditionNode last = lastWaiter; + if (last == null) + firstWaiter = node; + else + last.nextWaiter = node; + lastWaiter = node; + int savedState = getState(); + if (release(savedState)) + return savedState; + } + node.status = CANCELLED; // lock not held or inconsistent + throw new IllegalMonitorStateException(); + } + + /** + * Returns true if a node that was initially placed on a condition + * queue is now ready to reacquire on sync queue. + * @param node the node + * @return true if is reacquiring + */ + private boolean canReacquire(ConditionNode node) { + // check links, not status to avoid enqueue race + return node != null && node.prev != null && isEnqueued(node); + } + + /** + * Unlinks the given node and other non-waiting nodes from + * condition queue unless already unlinked. + */ + private void unlinkCancelledWaiters(ConditionNode node) { + if (node == null || node.nextWaiter != null || node == lastWaiter) { + ConditionNode w = firstWaiter, trail = null; + while (w != null) { + ConditionNode next = w.nextWaiter; + if ((w.status & COND) == 0) { + w.nextWaiter = null; + if (trail == null) + firstWaiter = next; + else + trail.nextWaiter = next; + if (next == null) + lastWaiter = trail; + } else + trail = w; + w = next; + } + } } /** @@ -2011,51 +1562,27 @@ * */ public final void awaitUninterruptibly() { - Node node = addConditionWaiter(); - int savedState = fullyRelease(node); + ConditionNode node = new ConditionNode(); + int savedState = enableWait(node); + LockSupport.setCurrentBlocker(this); // for back-compatibility boolean interrupted = false; - while (!isOnSyncQueue(node)) { - LockSupport.park(this); + while (!canReacquire(node)) { if (Thread.interrupted()) interrupted = true; + else if ((node.status & COND) != 0) { + try { + ForkJoinPool.managedBlock(node); + } catch (InterruptedException ie) { + interrupted = true; + } + } else + Thread.onSpinWait(); // awoke while enqueuing } - if (acquireQueued(node, savedState) || interrupted) - selfInterrupt(); - } - - /* - * For interruptible waits, we need to track whether to throw - * InterruptedException, if interrupted while blocked on - * condition, versus reinterrupt current thread, if - * interrupted while blocked waiting to re-acquire. - */ - - /** Mode meaning to reinterrupt on exit from wait */ - private static final int REINTERRUPT = 1; - /** Mode meaning to throw InterruptedException on exit from wait */ - private static final int THROW_IE = -1; - - /** - * Checks for interrupt, returning THROW_IE if interrupted - * before signalled, REINTERRUPT if after signalled, or - * 0 if not interrupted. - */ - private int checkInterruptWhileWaiting(Node node) { - return Thread.interrupted() ? - (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : - 0; - } - - /** - * Throws InterruptedException, reinterrupts current thread, or - * does nothing, depending on mode. - */ - private void reportInterruptAfterWait(int interruptMode) - throws InterruptedException { - if (interruptMode == THROW_IE) - throw new InterruptedException(); - else if (interruptMode == REINTERRUPT) - selfInterrupt(); + LockSupport.setCurrentBlocker(null); + node.clearStatus(); + acquire(node, savedState, false, false, false, 0L); + if (interrupted) + Thread.currentThread().interrupt(); } /** @@ -2074,20 +1601,33 @@ public final void await() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); - Node node = addConditionWaiter(); - int savedState = fullyRelease(node); - int interruptMode = 0; - while (!isOnSyncQueue(node)) { - LockSupport.park(this); - if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) - break; + ConditionNode node = new ConditionNode(); + int savedState = enableWait(node); + LockSupport.setCurrentBlocker(this); // for back-compatibility + boolean interrupted = false, cancelled = false; + while (!canReacquire(node)) { + if (interrupted |= Thread.interrupted()) { + if (cancelled = (node.getAndUnsetStatus(COND) & COND) != 0) + break; // else interrupted after signal + } else if ((node.status & COND) != 0) { + try { + ForkJoinPool.managedBlock(node); + } catch (InterruptedException ie) { + interrupted = true; + } + } else + Thread.onSpinWait(); // awoke while enqueuing } - if (acquireQueued(node, savedState) && interruptMode != THROW_IE) - interruptMode = REINTERRUPT; - if (node.nextWaiter != null) // clean up if cancelled - unlinkCancelledWaiters(); - if (interruptMode != 0) - reportInterruptAfterWait(interruptMode); + LockSupport.setCurrentBlocker(null); + node.clearStatus(); + acquire(node, savedState, false, false, false, 0L); + if (interrupted) { + if (cancelled) { + unlinkCancelledWaiters(node); + throw new InterruptedException(); + } + Thread.currentThread().interrupt(); + } } /** @@ -2107,32 +1647,29 @@ throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); - // We don't check for nanosTimeout <= 0L here, to allow - // awaitNanos(0) as a way to "yield the lock". - final long deadline = System.nanoTime() + nanosTimeout; - long initialNanos = nanosTimeout; - Node node = addConditionWaiter(); - int savedState = fullyRelease(node); - int interruptMode = 0; - while (!isOnSyncQueue(node)) { - if (nanosTimeout <= 0L) { - transferAfterCancelledWait(node); - break; - } - if (nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD) - LockSupport.parkNanos(this, nanosTimeout); - if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) - break; - nanosTimeout = deadline - System.nanoTime(); + ConditionNode node = new ConditionNode(); + int savedState = enableWait(node); + long nanos = (nanosTimeout < 0L) ? 0L : nanosTimeout; + long deadline = System.nanoTime() + nanos; + boolean cancelled = false, interrupted = false; + while (!canReacquire(node)) { + if ((interrupted |= Thread.interrupted()) || + (nanos = deadline - System.nanoTime()) <= 0L) { + if (cancelled = (node.getAndUnsetStatus(COND) & COND) != 0) + break; + } else + LockSupport.parkNanos(this, nanos); } - if (acquireQueued(node, savedState) && interruptMode != THROW_IE) - interruptMode = REINTERRUPT; - if (node.nextWaiter != null) - unlinkCancelledWaiters(); - if (interruptMode != 0) - reportInterruptAfterWait(interruptMode); + node.clearStatus(); + acquire(node, savedState, false, false, false, 0L); + if (cancelled) { + unlinkCancelledWaiters(node); + if (interrupted) + throw new InterruptedException(); + } else if (interrupted) + Thread.currentThread().interrupt(); long remaining = deadline - System.nanoTime(); // avoid overflow - return (remaining <= initialNanos) ? remaining : Long.MIN_VALUE; + return (remaining <= nanosTimeout) ? remaining : Long.MIN_VALUE; } /** @@ -2154,26 +1691,26 @@ long abstime = deadline.getTime(); if (Thread.interrupted()) throw new InterruptedException(); - Node node = addConditionWaiter(); - int savedState = fullyRelease(node); - boolean timedout = false; - int interruptMode = 0; - while (!isOnSyncQueue(node)) { - if (System.currentTimeMillis() >= abstime) { - timedout = transferAfterCancelledWait(node); - break; - } - LockSupport.parkUntil(this, abstime); - if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) - break; + ConditionNode node = new ConditionNode(); + int savedState = enableWait(node); + boolean cancelled = false, interrupted = false; + while (!canReacquire(node)) { + if ((interrupted |= Thread.interrupted()) || + System.currentTimeMillis() >= abstime) { + if (cancelled = (node.getAndUnsetStatus(COND) & COND) != 0) + break; + } else + LockSupport.parkUntil(this, abstime); } - if (acquireQueued(node, savedState) && interruptMode != THROW_IE) - interruptMode = REINTERRUPT; - if (node.nextWaiter != null) - unlinkCancelledWaiters(); - if (interruptMode != 0) - reportInterruptAfterWait(interruptMode); - return !timedout; + node.clearStatus(); + acquire(node, savedState, false, false, false, 0L); + if (cancelled) { + unlinkCancelledWaiters(node); + if (interrupted) + throw new InterruptedException(); + } else if (interrupted) + Thread.currentThread().interrupt(); + return !cancelled; } /** @@ -2195,31 +1732,28 @@ long nanosTimeout = unit.toNanos(time); if (Thread.interrupted()) throw new InterruptedException(); - // We don't check for nanosTimeout <= 0L here, to allow - // await(0, unit) as a way to "yield the lock". - final long deadline = System.nanoTime() + nanosTimeout; - Node node = addConditionWaiter(); - int savedState = fullyRelease(node); - boolean timedout = false; - int interruptMode = 0; - while (!isOnSyncQueue(node)) { - if (nanosTimeout <= 0L) { - timedout = transferAfterCancelledWait(node); - break; - } - if (nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD) - LockSupport.parkNanos(this, nanosTimeout); - if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) - break; - nanosTimeout = deadline - System.nanoTime(); + ConditionNode node = new ConditionNode(); + int savedState = enableWait(node); + long nanos = (nanosTimeout < 0L) ? 0L : nanosTimeout; + long deadline = System.nanoTime() + nanos; + boolean cancelled = false, interrupted = false; + while (!canReacquire(node)) { + if ((interrupted |= Thread.interrupted()) || + (nanos = deadline - System.nanoTime()) <= 0L) { + if (cancelled = (node.getAndUnsetStatus(COND) & COND) != 0) + break; + } else + LockSupport.parkNanos(this, nanos); } - if (acquireQueued(node, savedState) && interruptMode != THROW_IE) - interruptMode = REINTERRUPT; - if (node.nextWaiter != null) - unlinkCancelledWaiters(); - if (interruptMode != 0) - reportInterruptAfterWait(interruptMode); - return !timedout; + node.clearStatus(); + acquire(node, savedState, false, false, false, 0L); + if (cancelled) { + unlinkCancelledWaiters(node); + if (interrupted) + throw new InterruptedException(); + } else if (interrupted) + Thread.currentThread().interrupt(); + return !cancelled; } // support for instrumentation @@ -2245,8 +1779,8 @@ protected final boolean hasWaiters() { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); - for (Node w = firstWaiter; w != null; w = w.nextWaiter) { - if (w.waitStatus == Node.CONDITION) + for (ConditionNode w = firstWaiter; w != null; w = w.nextWaiter) { + if ((w.status & COND) != 0) return true; } return false; @@ -2265,8 +1799,8 @@ if (!isHeldExclusively()) throw new IllegalMonitorStateException(); int n = 0; - for (Node w = firstWaiter; w != null; w = w.nextWaiter) { - if (w.waitStatus == Node.CONDITION) + for (ConditionNode w = firstWaiter; w != null; w = w.nextWaiter) { + if ((w.status & COND) != 0) ++n; } return n; @@ -2285,9 +1819,9 @@ if (!isHeldExclusively()) throw new IllegalMonitorStateException(); ArrayList list = new ArrayList<>(); - for (Node w = firstWaiter; w != null; w = w.nextWaiter) { - if (w.waitStatus == Node.CONDITION) { - Thread t = w.thread; + for (ConditionNode w = firstWaiter; w != null; w = w.nextWaiter) { + if ((w.status & COND) != 0) { + Thread t = w.waiter; if (t != null) list.add(t); } @@ -2296,39 +1830,16 @@ } } - // VarHandle mechanics - private static final VarHandle STATE; - private static final VarHandle HEAD; - private static final VarHandle TAIL; + // Unsafe + private static final Unsafe U = Unsafe.getUnsafe(); + private static final long STATE + = U.objectFieldOffset(AbstractQueuedSynchronizer.class, "state"); + private static final long HEAD + = U.objectFieldOffset(AbstractQueuedSynchronizer.class, "head"); + private static final long TAIL + = U.objectFieldOffset(AbstractQueuedSynchronizer.class, "tail"); static { - try { - MethodHandles.Lookup l = MethodHandles.lookup(); - STATE = l.findVarHandle(AbstractQueuedSynchronizer.class, "state", int.class); - HEAD = l.findVarHandle(AbstractQueuedSynchronizer.class, "head", Node.class); - TAIL = l.findVarHandle(AbstractQueuedSynchronizer.class, "tail", Node.class); - } catch (ReflectiveOperationException e) { - throw new ExceptionInInitializerError(e); - } - - // Reduce the risk of rare disastrous classloading in first call to - // LockSupport.park: https://bugs.openjdk.java.net/browse/JDK-8074773 Class ensureLoaded = LockSupport.class; } - - /** - * Initializes head and tail fields on first contention. - */ - private final void initializeSyncQueue() { - Node h; - if (HEAD.compareAndSet(this, null, (h = new Node()))) - tail = h; - } - - /** - * CASes tail field. - */ - private final boolean compareAndSetTail(Node expect, Node update) { - return TAIL.compareAndSet(this, expect, update); - } } diff --git a/src/java.base/share/classes/java/util/concurrent/locks/Lock.java b/src/java.base/share/classes/java/util/concurrent/locks/Lock.java --- a/src/java.base/share/classes/java/util/concurrent/locks/Lock.java +++ b/src/java.base/share/classes/java/util/concurrent/locks/Lock.java @@ -122,9 +122,8 @@ *

All {@code Lock} implementations must enforce the same * memory synchronization semantics as provided by the built-in monitor * lock, as described in - * * Chapter 17 of - * The Java™ Language Specification: + * The Java™ Language Specification: *

    *
  • A successful {@code lock} operation has the same memory * synchronization effects as a successful Lock action. @@ -162,6 +161,7 @@ * @see ReentrantLock * @see Condition * @see ReadWriteLock + * @jls 17.4 Memory Model * * @since 1.5 * @author Doug Lea diff --git a/src/java.base/share/classes/java/util/concurrent/locks/LockSupport.java b/src/java.base/share/classes/java/util/concurrent/locks/LockSupport.java --- a/src/java.base/share/classes/java/util/concurrent/locks/LockSupport.java +++ b/src/java.base/share/classes/java/util/concurrent/locks/LockSupport.java @@ -140,8 +140,25 @@ private LockSupport() {} // Cannot be instantiated. private static void setBlocker(Thread t, Object arg) { - // Even though volatile, hotspot doesn't need a write barrier here. - U.putReference(t, PARKBLOCKER, arg); + U.putReferenceOpaque(t, PARKBLOCKER, arg); + } + + /** + * Sets the object to be returned by invocations of {@link + * #getBlocker getBlocker} for the current thread. This method may + * be used before invoking the no-argument version of {@link + * LockSupport#park() park()} from non-public objects, allowing + * more helpful diagnostics, or retaining compatibility with + * previous implementations of blocking methods. Previous values + * of the blocker are not automatically restored after blocking. + * To obtain the effects of {@code park(b}}, use {@code + * setCurrentBlocker(b); park(); setCurrentBlocker(null);} + * + * @param blocker the blocker object + * @since 14 + */ + public static void setCurrentBlocker(Object blocker) { + U.putReferenceOpaque(Thread.currentThread(), PARKBLOCKER, blocker); } /** @@ -292,7 +309,7 @@ public static Object getBlocker(Thread t) { if (t == null) throw new NullPointerException(); - return U.getReferenceVolatile(t, PARKBLOCKER); + return U.getReferenceOpaque(t, PARKBLOCKER); } /** @@ -394,24 +411,6 @@ } /** - * Returns the pseudo-randomly initialized or updated secondary seed. - * Copied from ThreadLocalRandom due to package access restrictions. - */ - static final int nextSecondarySeed() { - int r; - Thread t = Thread.currentThread(); - if ((r = U.getInt(t, SECONDARY)) != 0) { - r ^= r << 13; // xorshift - r ^= r >>> 17; - r ^= r << 5; - } - else if ((r = java.util.concurrent.ThreadLocalRandom.current().nextInt()) == 0) - r = 1; // avoid zero - U.putInt(t, SECONDARY, r); - return r; - } - - /** * Returns the thread id for the given thread. We must access * this directly rather than via method Thread.getId() because * getId() has been known to be overridden in ways that do not @@ -423,11 +422,9 @@ // Hotspot implementation via intrinsics API private static final Unsafe U = Unsafe.getUnsafe(); - private static final long PARKBLOCKER = U.objectFieldOffset - (Thread.class, "parkBlocker"); - private static final long SECONDARY = U.objectFieldOffset - (Thread.class, "threadLocalRandomSecondarySeed"); - private static final long TID = U.objectFieldOffset - (Thread.class, "tid"); + private static final long PARKBLOCKER + = U.objectFieldOffset(Thread.class, "parkBlocker"); + private static final long TID + = U.objectFieldOffset(Thread.class, "tid"); } diff --git a/src/java.base/share/classes/java/util/concurrent/locks/ReentrantLock.java b/src/java.base/share/classes/java/util/concurrent/locks/ReentrantLock.java --- a/src/java.base/share/classes/java/util/concurrent/locks/ReentrantLock.java +++ b/src/java.base/share/classes/java/util/concurrent/locks/ReentrantLock.java @@ -119,39 +119,63 @@ private static final long serialVersionUID = -5179523762034025860L; /** - * Performs non-fair tryLock. tryAcquire is implemented in - * subclasses, but both need nonfair try for trylock method. + * Performs non-fair tryLock. */ @ReservedStackAccess - final boolean nonfairTryAcquire(int acquires) { - final Thread current = Thread.currentThread(); + final boolean tryLock() { + Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { - if (compareAndSetState(0, acquires)) { + if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(current); return true; } - } - else if (current == getExclusiveOwnerThread()) { - int nextc = c + acquires; - if (nextc < 0) // overflow + } else if (getExclusiveOwnerThread() == current) { + if (++c < 0) // overflow throw new Error("Maximum lock count exceeded"); - setState(nextc); + setState(c); return true; } return false; } + /** + * Checks for reentrancy and acquires if lock immediately + * available under fair vs nonfair rules. Locking methods + * perform initialTryLock check before relaying to + * corresponding AQS acquire methods. + */ + abstract boolean initialTryLock(); + + @ReservedStackAccess + final void lock() { + if (!initialTryLock()) + acquire(1); + } + + @ReservedStackAccess + final void lockInterruptibly() throws InterruptedException { + if (Thread.interrupted()) + throw new InterruptedException(); + if (!initialTryLock()) + acquireInterruptibly(1); + } + + @ReservedStackAccess + final boolean tryLockNanos(long nanos) throws InterruptedException { + if (Thread.interrupted()) + throw new InterruptedException(); + return initialTryLock() || tryAcquireNanos(1, nanos); + } + @ReservedStackAccess protected final boolean tryRelease(int releases) { int c = getState() - releases; - if (Thread.currentThread() != getExclusiveOwnerThread()) + if (getExclusiveOwnerThread() != Thread.currentThread()) throw new IllegalMonitorStateException(); - boolean free = false; - if (c == 0) { - free = true; + boolean free = (c == 0); + if (free) setExclusiveOwnerThread(null); - } setState(c); return free; } @@ -195,8 +219,31 @@ */ static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; + + final boolean initialTryLock() { + Thread current = Thread.currentThread(); + if (compareAndSetState(0, 1)) { // first attempt is unguarded + setExclusiveOwnerThread(current); + return true; + } else if (getExclusiveOwnerThread() == current) { + int c = getState() + 1; + if (c < 0) // overflow + throw new Error("Maximum lock count exceeded"); + setState(c); + return true; + } else + return false; + } + + /** + * Acquire for non-reentrant cases after initialTryLock prescreen + */ protected final boolean tryAcquire(int acquires) { - return nonfairTryAcquire(acquires); + if (getState() == 0 && compareAndSetState(0, acquires)) { + setExclusiveOwnerThread(Thread.currentThread()); + return true; + } + return false; } } @@ -205,26 +252,34 @@ */ static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; + /** - * Fair version of tryAcquire. Don't grant access unless - * recursive call or no waiters or is first. + * Acquires only if reentrant or queue is empty. */ - @ReservedStackAccess - protected final boolean tryAcquire(int acquires) { - final Thread current = Thread.currentThread(); + final boolean initialTryLock() { + Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { - if (!hasQueuedPredecessors() && - compareAndSetState(0, acquires)) { + if (!hasQueuedThreads() && compareAndSetState(0, 1)) { setExclusiveOwnerThread(current); return true; } + } else if (getExclusiveOwnerThread() == current) { + if (++c < 0) // overflow + throw new Error("Maximum lock count exceeded"); + setState(c); + return true; } - else if (current == getExclusiveOwnerThread()) { - int nextc = c + acquires; - if (nextc < 0) - throw new Error("Maximum lock count exceeded"); - setState(nextc); + return false; + } + + /** + * Acquires only if thread is first waiter or empty + */ + protected final boolean tryAcquire(int acquires) { + if (getState() == 0 && !hasQueuedPredecessors() && + compareAndSetState(0, acquires)) { + setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; @@ -264,7 +319,7 @@ * at which time the lock hold count is set to one. */ public void lock() { - sync.acquire(1); + sync.lock(); } /** @@ -314,7 +369,7 @@ * @throws InterruptedException if the current thread is interrupted */ public void lockInterruptibly() throws InterruptedException { - sync.acquireInterruptibly(1); + sync.lockInterruptibly(); } /** @@ -344,7 +399,7 @@ * thread; and {@code false} otherwise */ public boolean tryLock() { - return sync.nonfairTryAcquire(1); + return sync.tryLock(); } /** @@ -421,7 +476,7 @@ */ public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { - return sync.tryAcquireNanos(1, unit.toNanos(timeout)); + return sync.tryLockNanos(unit.toNanos(timeout)); } /** diff --git a/src/java.base/share/classes/java/util/concurrent/locks/StampedLock.java b/src/java.base/share/classes/java/util/concurrent/locks/StampedLock.java --- a/src/java.base/share/classes/java/util/concurrent/locks/StampedLock.java +++ b/src/java.base/share/classes/java/util/concurrent/locks/StampedLock.java @@ -35,9 +35,8 @@ package java.util.concurrent.locks; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.VarHandle; import java.util.concurrent.TimeUnit; +import jdk.internal.misc.Unsafe; import jdk.internal.vm.annotation.ReservedStackAccess; /** @@ -132,9 +131,8 @@ * *

    Memory Synchronization. Methods with the effect of * successfully locking in any mode have the same memory - * synchronization effects as a Lock action described in - * - * Chapter 17 of The Java™ Language Specification. + * synchronization effects as a Lock action, as described in + * Chapter 17 of The Java™ Language Specification. * Methods successfully unlocking in write mode have the same memory * synchronization effects as an Unlock action. In optimistic * read usages, actions prior to the most recent write mode unlock action @@ -237,6 +235,7 @@ * } * }}

* + * @jls 17.4 Memory Model * @since 1.8 * @author Doug Lea */ @@ -264,122 +263,54 @@ * updates. * * Waiters use a modified form of CLH lock used in - * AbstractQueuedSynchronizer (see its internal documentation for - * a fuller account), where each node is tagged (field mode) as - * either a reader or writer. Sets of waiting readers are grouped - * (linked) under a common node (field cowait) so act as a single - * node with respect to most CLH mechanics. By virtue of the - * queue structure, wait nodes need not actually carry sequence - * numbers; we know each is greater than its predecessor. This - * simplifies the scheduling policy to a mainly-FIFO scheme that - * incorporates elements of Phase-Fair locks (see Brandenburg & - * Anderson, especially http://www.cs.unc.edu/~bbb/diss/). In - * particular, we use the phase-fair anti-barging rule: If an - * incoming reader arrives while read lock is held but there is a - * queued writer, this incoming reader is queued. (This rule is - * responsible for some of the complexity of method acquireRead, - * but without it, the lock becomes highly unfair.) Method release - * does not (and sometimes cannot) itself wake up cowaiters. This - * is done by the primary thread, but helped by any other threads - * with nothing better to do in methods acquireRead and - * acquireWrite. + * AbstractQueuedSynchronizer (AQS; see its internal documentation + * for a fuller account), where each node is either a ReaderNode + * or WriterNode. Implementation of queued Writer mode is + * identical to AQS except for lock-state operations. Sets of + * waiting readers are grouped (linked) under a common node (field + * cowaiters) so act as a single node with respect to most CLH + * mechanics. This simplifies the scheduling policy to a + * mainly-FIFO scheme that incorporates elements of Phase-Fair + * locks (see Brandenburg & Anderson, especially + * http://www.cs.unc.edu/~bbb/diss/). Method release does not + * itself wake up cowaiters. This is done by the primary thread, + * but helped by other cowaiters as they awaken. * - * These rules apply to threads actually queued. All tryLock forms - * opportunistically try to acquire locks regardless of preference - * rules, and so may "barge" their way in. Randomized spinning is - * used in the acquire methods to reduce (increasingly expensive) - * context switching while also avoiding sustained memory - * thrashing among many threads. We limit spins to the head of - * queue. If, upon wakening, a thread fails to obtain lock, and is - * still (or becomes) the first waiting thread (which indicates - * that some other thread barged and obtained lock), it escalates - * spins (up to MAX_HEAD_SPINS) to reduce the likelihood of - * continually losing to barging threads. + * These rules apply to threads actually queued. Threads may also + * try to acquire locks before or in the process of enqueueing + * regardless of preference rules, and so may "barge" their way + * in. Methods writeLock and readLock (but not the other variants + * of each) first unconditionally try to CAS state, falling back + * to test-and-test-and-set retries on failure, slightly shrinking + * race windows on initial attempts, thus making success more + * likely. Also, when some threads cancel (via interrupt or + * timeout), phase-fairness is at best roughly approximated. * * Nearly all of these mechanics are carried out in methods * acquireWrite and acquireRead, that, as typical of such code, * sprawl out because actions and retries rely on consistent sets * of locally cached reads. * - * As noted in Boehm's paper (above), sequence validation (mainly - * method validate()) requires stricter ordering rules than apply - * to normal volatile reads (of "state"). To force orderings of - * reads before a validation and the validation itself in those - * cases where this is not already forced, we use acquireFence. - * Unlike in that paper, we allow writers to use plain writes. - * One would not expect reorderings of such writes with the lock - * acquisition CAS because there is a "control dependency", but it - * is theoretically possible, so we additionally add a - * storeStoreFence after lock acquisition CAS. - * - * ---------------------------------------------------------------- - * Here's an informal proof that plain reads by _successful_ - * readers see plain writes from preceding but not following - * writers (following Boehm and the C++ standard [atomics.fences]): - * - * Because of the total synchronization order of accesses to - * volatile long state containing the sequence number, writers and - * _successful_ readers can be globally sequenced. - * - * int x, y; - * - * Writer 1: - * inc sequence (odd - "locked") - * storeStoreFence(); - * x = 1; y = 2; - * inc sequence (even - "unlocked") - * - * Successful Reader: - * read sequence (even) - * // must see writes from Writer 1 but not Writer 2 - * r1 = x; r2 = y; - * acquireFence(); - * read sequence (even - validated unchanged) - * // use r1 and r2 - * - * Writer 2: - * inc sequence (odd - "locked") - * storeStoreFence(); - * x = 3; y = 4; - * inc sequence (even - "unlocked") - * - * Visibility of writer 1's stores is normal - reader's initial - * read of state synchronizes with writer 1's final write to state. - * Lack of visibility of writer 2's plain writes is less obvious. - * If reader's read of x or y saw writer 2's write, then (assuming - * semantics of C++ fences) the storeStoreFence would "synchronize" - * with reader's acquireFence and reader's validation read must see - * writer 2's initial write to state and so validation must fail. - * But making this "proof" formal and rigorous is an open problem! - * ---------------------------------------------------------------- + * For an explanation of the use of acquireFence, see + * http://gee.cs.oswego.edu/dl/html/j9mm.html as well as Boehm's + * paper (above). Note that sequence validation (mainly method + * validate()) requires stricter ordering rules than apply to + * normal volatile reads (of "state"). To ensure that writeLock + * acquisitions strictly precede subsequent writes in cases where + * this is not already forced, we use a storeStoreFence. * * The memory layout keeps lock state and queue pointers together * (normally on the same cache line). This usually works well for * read-mostly loads. In most other cases, the natural tendency of - * adaptive-spin CLH locks to reduce memory contention lessens - * motivation to further spread out contended locations, but might - * be subject to future improvements. + * CLH locks to reduce memory contention lessens motivation to + * further spread out contended locations, but might be subject to + * future improvements. */ private static final long serialVersionUID = -6001602636862214147L; - /** Number of processors, for spin control */ - private static final int NCPU = Runtime.getRuntime().availableProcessors(); - - /** Maximum number of retries before enqueuing on acquisition; at least 1 */ - private static final int SPINS = (NCPU > 1) ? 1 << 6 : 1; - - /** Maximum number of tries before blocking at head on acquisition */ - private static final int HEAD_SPINS = (NCPU > 1) ? 1 << 10 : 1; - - /** Maximum number of retries before re-blocking */ - private static final int MAX_HEAD_SPINS = (NCPU > 1) ? 1 << 16 : 1; - - /** The period for yielding when waiting for overflow spinlock */ - private static final int OVERFLOW_YIELD_RATE = 7; // must be power 2 - 1 - /** The number of bits to use for reader count before overflowing */ - private static final int LG_READERS = 7; + private static final int LG_READERS = 7; // 127 readers // Values for lock state and stamp operations private static final long RUNIT = 1L; @@ -388,6 +319,8 @@ private static final long RFULL = RBITS - 1L; private static final long ABITS = RBITS | WBIT; private static final long SBITS = ~RBITS; // note overlap with ABITS + // not writing and conservatively non-overflowing + private static final long RSAFE = ~(3L << (LG_READERS - 1)); /* * 3 stamp modes can be distinguished by examining (m = stamp & ABITS): @@ -408,29 +341,64 @@ // Special value from cancelled acquire methods so caller can throw IE private static final long INTERRUPTED = 1L; - // Values for node status; order matters - private static final int WAITING = -1; - private static final int CANCELLED = 1; - - // Modes for nodes (int not boolean to allow arithmetic) - private static final int RMODE = 0; - private static final int WMODE = 1; + // Bits for Node.status + static final int WAITING = 1; + static final int CANCELLED = 0x80000000; // must be negative - /** Wait nodes */ - static final class WNode { - volatile WNode prev; - volatile WNode next; - volatile WNode cowait; // list of linked readers - volatile Thread thread; // non-null while possibly parked - volatile int status; // 0, WAITING, or CANCELLED - final int mode; // RMODE or WMODE - WNode(int m, WNode p) { mode = m; prev = p; } + /** CLH nodes */ + abstract static class Node { + volatile Node prev; // initially attached via casTail + volatile Node next; // visibly nonnull when signallable + Thread waiter; // visibly nonnull when enqueued + volatile int status; // written by owner, atomic bit ops by others + + // methods for atomic operations + final boolean casPrev(Node c, Node v) { // for cleanQueue + return U.weakCompareAndSetReference(this, PREV, c, v); + } + final boolean casNext(Node c, Node v) { // for cleanQueue + return U.weakCompareAndSetReference(this, NEXT, c, v); + } + final int getAndUnsetStatus(int v) { // for signalling + return U.getAndBitwiseAndInt(this, STATUS, ~v); + } + final void setPrevRelaxed(Node p) { // for off-queue assignment + U.putReference(this, PREV, p); + } + final void setStatusRelaxed(int s) { // for off-queue assignment + U.putInt(this, STATUS, s); + } + final void clearStatus() { // for reducing unneeded signals + U.putIntOpaque(this, STATUS, 0); + } + + private static final long STATUS + = U.objectFieldOffset(Node.class, "status"); + private static final long NEXT + = U.objectFieldOffset(Node.class, "next"); + private static final long PREV + = U.objectFieldOffset(Node.class, "prev"); + } + + static final class WriterNode extends Node { // node for writers + } + + static final class ReaderNode extends Node { // node for readers + volatile ReaderNode cowaiters; // list of linked readers + final boolean casCowaiters(ReaderNode c, ReaderNode v) { + return U.weakCompareAndSetReference(this, COWAITERS, c, v); + } + final void setCowaitersRelaxed(ReaderNode p) { + U.putReference(this, COWAITERS, p); + } + private static final long COWAITERS + = U.objectFieldOffset(ReaderNode.class, "cowaiters"); } /** Head of CLH queue */ - private transient volatile WNode whead; + private transient volatile Node head; /** Tail (last) of CLH queue */ - private transient volatile WNode wtail; + private transient volatile Node tail; // views transient ReadLockView readLockView; @@ -449,20 +417,52 @@ state = ORIGIN; } - private boolean casState(long expectedValue, long newValue) { - return STATE.compareAndSet(this, expectedValue, newValue); + // internal lock methods + + private boolean casState(long expect, long update) { + return U.compareAndSetLong(this, STATE, expect, update); } - private long tryWriteLock(long s) { - // assert (s & ABITS) == 0L; - long next; - if (casState(s, next = s | WBIT)) { - VarHandle.storeStoreFence(); - return next; + @ReservedStackAccess + private long tryAcquireWrite() { + long s, nextState; + if (((s = state) & ABITS) == 0L && casState(s, nextState = s | WBIT)) { + U.storeStoreFence(); + return nextState; } return 0L; } + @ReservedStackAccess + private long tryAcquireRead() { + for (long s, m, nextState;;) { + if ((m = (s = state) & ABITS) < RFULL) { + if (casState(s, nextState = s + RUNIT)) + return nextState; + } + else if (m == WBIT) + return 0L; + else if ((nextState = tryIncReaderOverflow(s)) != 0L) + return nextState; + } + } + + /** + * Returns an unlocked state, incrementing the version and + * avoiding special failure value 0L. + * + * @param s a write-locked state (or stamp) + */ + private static long unlockWriteState(long s) { + return ((s += WBIT) == 0L) ? ORIGIN : s; + } + + private long releaseWrite(long s) { + long nextState = state = unlockWriteState(s); + signalNext(head); + return nextState; + } + /** * Exclusively acquires the lock, blocking if necessary * until available. @@ -471,8 +471,13 @@ */ @ReservedStackAccess public long writeLock() { - long next; - return ((next = tryWriteLock()) != 0L) ? next : acquireWrite(false, 0L); + // try unconditional CAS confirming weak read + long s = U.getLongOpaque(this, STATE) & ~ABITS, nextState; + if (casState(s, nextState = s | WBIT)) { + U.storeStoreFence(); + return nextState; + } + return acquireWrite(false, false, 0L); } /** @@ -481,10 +486,8 @@ * @return a write stamp that can be used to unlock or convert mode, * or zero if the lock is not available */ - @ReservedStackAccess public long tryWriteLock() { - long s; - return (((s = state) & ABITS) == 0L) ? tryWriteLock(s) : 0L; + return tryAcquireWrite(); } /** @@ -504,15 +507,14 @@ throws InterruptedException { long nanos = unit.toNanos(time); if (!Thread.interrupted()) { - long next, deadline; - if ((next = tryWriteLock()) != 0L) - return next; + long nextState; + if ((nextState = tryAcquireWrite()) != 0L) + return nextState; if (nanos <= 0L) return 0L; - if ((deadline = System.nanoTime() + nanos) == 0L) - deadline = 1L; - if ((next = acquireWrite(true, deadline)) != INTERRUPTED) - return next; + nextState = acquireWrite(true, true, System.nanoTime() + nanos); + if (nextState != INTERRUPTED) + return nextState; } throw new InterruptedException(); } @@ -527,12 +529,12 @@ * @throws InterruptedException if the current thread is interrupted * before acquiring the lock */ - @ReservedStackAccess public long writeLockInterruptibly() throws InterruptedException { - long next; + long nextState; if (!Thread.interrupted() && - (next = acquireWrite(true, 0L)) != INTERRUPTED) - return next; + ((nextState = tryAcquireWrite()) != 0L || + (nextState = acquireWrite(true, false, 0L)) != INTERRUPTED)) + return nextState; throw new InterruptedException(); } @@ -544,13 +546,12 @@ */ @ReservedStackAccess public long readLock() { - long s, next; - // bypass acquireRead on common uncontended case - return (whead == wtail - && ((s = state) & ABITS) < RFULL - && casState(s, next = s + RUNIT)) - ? next - : acquireRead(false, 0L); + // unconditionally optimistically try non-overflow case once + long s = U.getLongOpaque(this, STATE) & RSAFE, nextState; + if (casState(s, nextState = s + RUNIT)) + return nextState; + else + return acquireRead(false, false, 0L); } /** @@ -559,18 +560,8 @@ * @return a read stamp that can be used to unlock or convert mode, * or zero if the lock is not available */ - @ReservedStackAccess public long tryReadLock() { - long s, m, next; - while ((m = (s = state) & ABITS) != WBIT) { - if (m < RFULL) { - if (casState(s, next = s + RUNIT)) - return next; - } - else if ((next = tryIncReaderOverflow(s)) != 0L) - return next; - } - return 0L; + return tryAcquireRead(); } /** @@ -586,26 +577,18 @@ * @throws InterruptedException if the current thread is interrupted * before acquiring the lock */ - @ReservedStackAccess public long tryReadLock(long time, TimeUnit unit) throws InterruptedException { - long s, m, next, deadline; long nanos = unit.toNanos(time); if (!Thread.interrupted()) { - if ((m = (s = state) & ABITS) != WBIT) { - if (m < RFULL) { - if (casState(s, next = s + RUNIT)) - return next; - } - else if ((next = tryIncReaderOverflow(s)) != 0L) - return next; - } + long nextState; + if (tail == head && (nextState = tryAcquireRead()) != 0L) + return nextState; if (nanos <= 0L) return 0L; - if ((deadline = System.nanoTime() + nanos) == 0L) - deadline = 1L; - if ((next = acquireRead(true, deadline)) != INTERRUPTED) - return next; + nextState = acquireRead(true, true, System.nanoTime() + nanos); + if (nextState != INTERRUPTED) + return nextState; } throw new InterruptedException(); } @@ -620,17 +603,12 @@ * @throws InterruptedException if the current thread is interrupted * before acquiring the lock */ - @ReservedStackAccess public long readLockInterruptibly() throws InterruptedException { - long s, next; - if (!Thread.interrupted() - // bypass acquireRead on common uncontended case - && ((whead == wtail - && ((s = state) & ABITS) < RFULL - && casState(s, next = s + RUNIT)) - || - (next = acquireRead(true, 0L)) != INTERRUPTED)) - return next; + long nextState; + if (!Thread.interrupted() && + ((nextState = tryAcquireRead()) != 0L || + (nextState = acquireRead(true, false, 0L)) != INTERRUPTED)) + return nextState; throw new InterruptedException(); } @@ -658,29 +636,11 @@ * since issuance of the given stamp; else false */ public boolean validate(long stamp) { - VarHandle.acquireFence(); + U.loadFence(); return (stamp & SBITS) == (state & SBITS); } /** - * Returns an unlocked state, incrementing the version and - * avoiding special failure value 0L. - * - * @param s a write-locked state (or stamp) - */ - private static long unlockWriteState(long s) { - return ((s += WBIT) == 0L) ? ORIGIN : s; - } - - private long unlockWriteInternal(long s) { - long next; WNode h; - STATE.setVolatile(this, next = unlockWriteState(s)); - if ((h = whead) != null && h.status != 0) - release(h); - return next; - } - - /** * If the lock state matches the given stamp, releases the * exclusive lock. * @@ -692,7 +652,7 @@ public void unlockWrite(long stamp) { if (state != stamp || (stamp & WBIT) == 0L) throw new IllegalMonitorStateException(); - unlockWriteInternal(stamp); + releaseWrite(stamp); } /** @@ -705,19 +665,20 @@ */ @ReservedStackAccess public void unlockRead(long stamp) { - long s, m; WNode h; - while (((s = state) & SBITS) == (stamp & SBITS) - && (stamp & RBITS) > 0L - && ((m = s & RBITS) > 0L)) { - if (m < RFULL) { - if (casState(s, s - RUNIT)) { - if (m == RUNIT && (h = whead) != null && h.status != 0) - release(h); + long s, m; + if ((stamp & RBITS) != 0L) { + while (((s = state) & SBITS) == (stamp & SBITS) && + ((m = s & RBITS) != 0L)) { + if (m < RFULL) { + if (casState(s, s - RUNIT)) { + if (m == RUNIT) + signalNext(head); + return; + } + } + else if (tryDecReaderOverflow(s) != 0L) return; - } } - else if (tryDecReaderOverflow(s) != 0L) - return; } throw new IllegalMonitorStateException(); } @@ -730,7 +691,6 @@ * @throws IllegalMonitorStateException if the stamp does * not match the current state of this lock */ - @ReservedStackAccess public void unlock(long stamp) { if ((stamp & WBIT) != 0L) unlockWrite(stamp); @@ -751,26 +711,23 @@ * @return a valid write stamp, or zero on failure */ public long tryConvertToWriteLock(long stamp) { - long a = stamp & ABITS, m, s, next; + long a = stamp & ABITS, m, s, nextState; while (((s = state) & SBITS) == (stamp & SBITS)) { if ((m = s & ABITS) == 0L) { if (a != 0L) break; - if ((next = tryWriteLock(s)) != 0L) - return next; - } - else if (m == WBIT) { + if (casState(s, nextState = s | WBIT)) { + U.storeStoreFence(); + return nextState; + } + } else if (m == WBIT) { if (a != m) break; return stamp; - } - else if (m == RUNIT && a != 0L) { - if (casState(s, next = s - RUNIT + WBIT)) { - VarHandle.storeStoreFence(); - return next; - } - } - else + } else if (m == RUNIT && a != 0L) { + if (casState(s, nextState = s - RUNIT + WBIT)) + return nextState; + } else break; } return 0L; @@ -788,28 +745,21 @@ * @return a valid read stamp, or zero on failure */ public long tryConvertToReadLock(long stamp) { - long a, s, next; WNode h; + long a, s, nextState; while (((s = state) & SBITS) == (stamp & SBITS)) { if ((a = stamp & ABITS) >= WBIT) { - // write stamp - if (s != stamp) + if (s != stamp) // write stamp break; - STATE.setVolatile(this, next = unlockWriteState(s) + RUNIT); - if ((h = whead) != null && h.status != 0) - release(h); - return next; - } - else if (a == 0L) { - // optimistic read stamp + nextState = state = unlockWriteState(s) + RUNIT; + signalNext(head); + return nextState; + } else if (a == 0L) { // optimistic read stamp if ((s & ABITS) < RFULL) { - if (casState(s, next = s + RUNIT)) - return next; - } - else if ((next = tryIncReaderOverflow(s)) != 0L) - return next; - } - else { - // already a read stamp + if (casState(s, nextState = s + RUNIT)) + return nextState; + } else if ((nextState = tryIncReaderOverflow(s)) != 0L) + return nextState; + } else { // already a read stamp if ((s & ABITS) == 0L) break; return stamp; @@ -829,29 +779,25 @@ * @return a valid optimistic read stamp, or zero on failure */ public long tryConvertToOptimisticRead(long stamp) { - long a, m, s, next; WNode h; - VarHandle.acquireFence(); + long a, m, s, nextState; + U.loadFence(); while (((s = state) & SBITS) == (stamp & SBITS)) { if ((a = stamp & ABITS) >= WBIT) { - // write stamp - if (s != stamp) + if (s != stamp) // write stamp break; - return unlockWriteInternal(s); - } - else if (a == 0L) - // already an optimistic read stamp + return releaseWrite(s); + } else if (a == 0L) { // already an optimistic read stamp return stamp; - else if ((m = s & ABITS) == 0L) // invalid read stamp + } else if ((m = s & ABITS) == 0L) { // invalid read stamp break; - else if (m < RFULL) { - if (casState(s, next = s - RUNIT)) { - if (m == RUNIT && (h = whead) != null && h.status != 0) - release(h); - return next & SBITS; + } else if (m < RFULL) { + if (casState(s, nextState = s - RUNIT)) { + if (m == RUNIT) + signalNext(head); + return nextState & SBITS; } - } - else if ((next = tryDecReaderOverflow(s)) != 0L) - return next & SBITS; + } else if ((nextState = tryDecReaderOverflow(s)) != 0L) + return nextState & SBITS; } return 0L; } @@ -867,7 +813,7 @@ public boolean tryUnlockWrite() { long s; if (((s = state) & WBIT) != 0L) { - unlockWriteInternal(s); + releaseWrite(s); return true; } return false; @@ -882,12 +828,12 @@ */ @ReservedStackAccess public boolean tryUnlockRead() { - long s, m; WNode h; + long s, m; while ((m = (s = state) & ABITS) != 0L && m < WBIT) { if (m < RFULL) { if (casState(s, s - RUNIT)) { - if (m == RUNIT && (h = whead) != null && h.status != 0) - release(h); + if (m == RUNIT) + signalNext(head); return true; } } @@ -1133,16 +1079,16 @@ long s; if (((s = state) & WBIT) == 0L) throw new IllegalMonitorStateException(); - unlockWriteInternal(s); + releaseWrite(s); } final void unstampedUnlockRead() { - long s, m; WNode h; + long s, m; while ((m = (s = state) & RBITS) > 0L) { if (m < RFULL) { if (casState(s, s - RUNIT)) { - if (m == RUNIT && (h = whead) != null && h.status != 0) - release(h); + if (m == RUNIT) + signalNext(head); return; } } @@ -1155,10 +1101,10 @@ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); - STATE.setVolatile(this, ORIGIN); // reset to unlocked state + state = ORIGIN; // reset to unlocked state } - // internals + // overflow handling methods /** * Tries to increment readerOverflow by first setting state @@ -1170,17 +1116,12 @@ */ private long tryIncReaderOverflow(long s) { // assert (s & ABITS) >= RFULL; - if ((s & ABITS) == RFULL) { - if (casState(s, s | RBITS)) { - ++readerOverflow; - STATE.setVolatile(this, s); - return s; - } + if ((s & ABITS) != RFULL) + Thread.onSpinWait(); + else if (casState(s, s | RBITS)) { + ++readerOverflow; + return state = s; } - else if ((LockSupport.nextSecondarySeed() & OVERFLOW_YIELD_RATE) == 0) - Thread.yield(); - else - Thread.onSpinWait(); return 0L; } @@ -1192,153 +1133,132 @@ */ private long tryDecReaderOverflow(long s) { // assert (s & ABITS) >= RFULL; - if ((s & ABITS) == RFULL) { - if (casState(s, s | RBITS)) { - int r; long next; - if ((r = readerOverflow) > 0) { - readerOverflow = r - 1; - next = s; - } - else - next = s - RUNIT; - STATE.setVolatile(this, next); - return next; + if ((s & ABITS) != RFULL) + Thread.onSpinWait(); + else if (casState(s, s | RBITS)) { + int r; long nextState; + if ((r = readerOverflow) > 0) { + readerOverflow = r - 1; + nextState = s; } + else + nextState = s - RUNIT; + return state = nextState; } - else if ((LockSupport.nextSecondarySeed() & OVERFLOW_YIELD_RATE) == 0) - Thread.yield(); - else - Thread.onSpinWait(); return 0L; } + // release methods + /** - * Wakes up the successor of h (normally whead). This is normally - * just h.next, but may require traversal from wtail if next - * pointers are lagging. This may fail to wake up an acquiring - * thread when one or more have been cancelled, but the cancel - * methods themselves provide extra safeguards to ensure liveness. + * Wakes up the successor of given node, if one exists, and unsets its + * WAITING status to avoid park race. This may fail to wake up an + * eligible thread when one or more have been cancelled, but + * cancelAcquire ensures liveness. */ - private void release(WNode h) { - if (h != null) { - WNode q; Thread w; - WSTATUS.compareAndSet(h, WAITING, 0); - if ((q = h.next) == null || q.status == CANCELLED) { - for (WNode t = wtail; t != null && t != h; t = t.prev) - if (t.status <= 0) - q = t; - } - if (q != null && (w = q.thread) != null) - LockSupport.unpark(w); + static final void signalNext(Node h) { + Node s; + if (h != null && (s = h.next) != null && s.status > 0) { + s.getAndUnsetStatus(WAITING); + LockSupport.unpark(s.waiter); } } /** - * See above for explanation. + * Removes and unparks all cowaiters of node, if it exists. + */ + private static void signalCowaiters(ReaderNode node) { + if (node != null) { + for (ReaderNode c; (c = node.cowaiters) != null; ) { + if (node.casCowaiters(c, c.cowaiters)) + LockSupport.unpark(c.waiter); + } + } + } + + // queue link methods + private boolean casTail(Node c, Node v) { + return U.compareAndSetReference(this, TAIL, c, v); + } + + /** tries once to CAS a new dummy node for head */ + private void tryInitializeHead() { + Node h = new WriterNode(); + if (U.compareAndSetReference(this, HEAD, null, h)) + tail = h; + } + + /** + * For explanation, see above and AbstractQueuedSynchronizer + * internal documentation. * * @param interruptible true if should check interrupts and if so * return INTERRUPTED - * @param deadline if nonzero, the System.nanoTime value to timeout - * at (and return zero) + * @param timed if true use timed waits + * @param time the System.nanoTime value to timeout at (and return zero) * @return next state, or INTERRUPTED */ - private long acquireWrite(boolean interruptible, long deadline) { - WNode node = null, p; - for (int spins = -1;;) { // spin while enqueuing - long m, s, ns; - if ((m = (s = state) & ABITS) == 0L) { - if ((ns = tryWriteLock(s)) != 0L) - return ns; + private long acquireWrite(boolean interruptible, boolean timed, long time) { + byte spins = 0, postSpins = 0; // retries upon unpark of first thread + boolean interrupted = false, first = false; + WriterNode node = null; + Node pred = null; + for (long s, nextState;;) { + if (!first && (pred = (node == null) ? null : node.prev) != null && + !(first = (head == pred))) { + if (pred.status < 0) { + cleanQueue(); // predecessor cancelled + continue; + } else if (pred.prev == null) { + Thread.onSpinWait(); // ensure serialization + continue; + } } - else if (spins < 0) - spins = (m == WBIT && wtail == whead) ? SPINS : 0; - else if (spins > 0) { + if ((first || pred == null) && ((s = state) & ABITS) == 0L && + casState(s, nextState = s | WBIT)) { + U.storeStoreFence(); + if (first) { + node.prev = null; + head = node; + pred.next = null; + node.waiter = null; + if (interrupted) + Thread.currentThread().interrupt(); + } + return nextState; + } else if (node == null) { // retry before enqueuing + node = new WriterNode(); + } else if (pred == null) { // try to enqueue + Node t = tail; + node.setPrevRelaxed(t); + if (t == null) + tryInitializeHead(); + else if (!casTail(t, node)) + node.setPrevRelaxed(null); // back out + else + t.next = node; + } else if (first && spins != 0) { // reduce unfairness --spins; Thread.onSpinWait(); - } - else if ((p = wtail) == null) { // initialize queue - WNode hd = new WNode(WMODE, null); - if (WHEAD.weakCompareAndSet(this, null, hd)) - wtail = hd; - } - else if (node == null) - node = new WNode(WMODE, p); - else if (node.prev != p) - node.prev = p; - else if (WTAIL.weakCompareAndSet(this, p, node)) { - p.next = node; - break; + } else if (node.status == 0) { // enable signal + if (node.waiter == null) + node.waiter = Thread.currentThread(); + node.status = WAITING; + } else { + long nanos; + spins = postSpins = (byte)((postSpins << 1) | 1); + if (!timed) + LockSupport.park(this); + else if ((nanos = time - System.nanoTime()) > 0L) + LockSupport.parkNanos(this, nanos); + else + break; + node.clearStatus(); + if ((interrupted |= Thread.interrupted()) && interruptible) + break; } } - - boolean wasInterrupted = false; - for (int spins = -1;;) { - WNode h, np, pp; int ps; - if ((h = whead) == p) { - if (spins < 0) - spins = HEAD_SPINS; - else if (spins < MAX_HEAD_SPINS) - spins <<= 1; - for (int k = spins; k > 0; --k) { // spin at head - long s, ns; - if (((s = state) & ABITS) == 0L) { - if ((ns = tryWriteLock(s)) != 0L) { - whead = node; - node.prev = null; - if (wasInterrupted) - Thread.currentThread().interrupt(); - return ns; - } - } - else - Thread.onSpinWait(); - } - } - else if (h != null) { // help release stale waiters - WNode c; Thread w; - while ((c = h.cowait) != null) { - if (WCOWAIT.weakCompareAndSet(h, c, c.cowait) && - (w = c.thread) != null) - LockSupport.unpark(w); - } - } - if (whead == h) { - if ((np = node.prev) != p) { - if (np != null) - (p = np).next = node; // stale - } - else if ((ps = p.status) == 0) - WSTATUS.compareAndSet(p, 0, WAITING); - else if (ps == CANCELLED) { - if ((pp = p.prev) != null) { - node.prev = pp; - pp.next = node; - } - } - else { - long time; // 0 argument to park means no timeout - if (deadline == 0L) - time = 0L; - else if ((time = deadline - System.nanoTime()) <= 0L) - return cancelWaiter(node, node, false); - Thread wt = Thread.currentThread(); - node.thread = wt; - if (p.status < 0 && (p != h || (state & ABITS) != 0L) && - whead == h && node.prev == p) { - if (time == 0L) - LockSupport.park(this); - else - LockSupport.parkNanos(this, time); - } - node.thread = null; - if (Thread.interrupted()) { - if (interruptible) - return cancelWaiter(node, node, true); - wasInterrupted = true; - } - } - } - } + return cancelAcquire(node, interrupted); } /** @@ -1346,182 +1266,178 @@ * * @param interruptible true if should check interrupts and if so * return INTERRUPTED - * @param deadline if nonzero, the System.nanoTime value to timeout - * at (and return zero) + * @param timed if true use timed waits + * @param time the System.nanoTime value to timeout at (and return zero) * @return next state, or INTERRUPTED */ - private long acquireRead(boolean interruptible, long deadline) { - boolean wasInterrupted = false; - WNode node = null, p; - for (int spins = -1;;) { - WNode h; - if ((h = whead) == (p = wtail)) { - for (long m, s, ns;;) { - if ((m = (s = state) & ABITS) < RFULL ? - casState(s, ns = s + RUNIT) : - (m < WBIT && (ns = tryIncReaderOverflow(s)) != 0L)) { - if (wasInterrupted) - Thread.currentThread().interrupt(); - return ns; + private long acquireRead(boolean interruptible, boolean timed, long time) { + boolean interrupted = false; + ReaderNode node = null; + /* + * Loop: + * if empty, try to acquire + * if tail is Reader, try to cowait; restart if leader stale or cancels + * else try to create and enqueue node, and wait in 2nd loop below + */ + for (;;) { + ReaderNode leader; long nextState; + Node tailPred = null, t = tail; + if ((t == null || (tailPred = t.prev) == null) && + (nextState = tryAcquireRead()) != 0L) // try now if empty + return nextState; + else if (t == null) + tryInitializeHead(); + else if (tailPred == null || !(t instanceof ReaderNode)) { + if (node == null) + node = new ReaderNode(); + if (tail == t) { + node.setPrevRelaxed(t); + if (casTail(t, node)) { + t.next = node; + break; // node is leader; wait in loop below } - else if (m >= WBIT) { - if (spins > 0) { - --spins; - Thread.onSpinWait(); - } - else { - if (spins == 0) { - WNode nh = whead, np = wtail; - if ((nh == h && np == p) || (h = nh) != (p = np)) - break; - } - spins = SPINS; - } + node.setPrevRelaxed(null); + } + } else if ((leader = (ReaderNode)t) == tail) { // try to cowait + for (boolean attached = false;;) { + if (leader.status < 0 || leader.prev == null) + break; + else if (node == null) + node = new ReaderNode(); + else if (node.waiter == null) + node.waiter = Thread.currentThread(); + else if (!attached) { + ReaderNode c = leader.cowaiters; + node.setCowaitersRelaxed(c); + attached = leader.casCowaiters(c, node); + if (!attached) + node.setCowaitersRelaxed(null); + } else { + long nanos = 0L; + if (!timed) + LockSupport.park(this); + else if ((nanos = time - System.nanoTime()) > 0L) + LockSupport.parkNanos(this, nanos); + interrupted |= Thread.interrupted(); + if ((interrupted && interruptible) || + (timed && nanos <= 0L)) + return cancelCowaiter(node, leader, interrupted); } } - } - if (p == null) { // initialize queue - WNode hd = new WNode(WMODE, null); - if (WHEAD.weakCompareAndSet(this, null, hd)) - wtail = hd; - } - else if (node == null) - node = new WNode(RMODE, p); - else if (h == p || p.mode != RMODE) { - if (node.prev != p) - node.prev = p; - else if (WTAIL.weakCompareAndSet(this, p, node)) { - p.next = node; - break; - } - } - else if (!WCOWAIT.compareAndSet(p, node.cowait = p.cowait, node)) - node.cowait = null; - else { - for (;;) { - WNode pp, c; Thread w; - if ((h = whead) != null && (c = h.cowait) != null && - WCOWAIT.compareAndSet(h, c, c.cowait) && - (w = c.thread) != null) // help release - LockSupport.unpark(w); - if (Thread.interrupted()) { - if (interruptible) - return cancelWaiter(node, p, true); - wasInterrupted = true; - } - if (h == (pp = p.prev) || h == p || pp == null) { - long m, s, ns; - do { - if ((m = (s = state) & ABITS) < RFULL ? - casState(s, ns = s + RUNIT) : - (m < WBIT && - (ns = tryIncReaderOverflow(s)) != 0L)) { - if (wasInterrupted) - Thread.currentThread().interrupt(); - return ns; - } - } while (m < WBIT); - } - if (whead == h && p.prev == pp) { - long time; - if (pp == null || h == p || p.status > 0) { - node = null; // throw away - break; - } - if (deadline == 0L) - time = 0L; - else if ((time = deadline - System.nanoTime()) <= 0L) { - if (wasInterrupted) - Thread.currentThread().interrupt(); - return cancelWaiter(node, p, false); - } - Thread wt = Thread.currentThread(); - node.thread = wt; - if ((h != pp || (state & ABITS) == WBIT) && - whead == h && p.prev == pp) { - if (time == 0L) - LockSupport.park(this); - else - LockSupport.parkNanos(this, time); - } - node.thread = null; - } - } + if (node != null) + node.waiter = null; + long ns = tryAcquireRead(); + signalCowaiters(leader); + if (interrupted) + Thread.currentThread().interrupt(); + if (ns != 0L) + return ns; + else + node = null; // restart if stale, missed, or leader cancelled } } - for (int spins = -1;;) { - WNode h, np, pp; int ps; - if ((h = whead) == p) { - if (spins < 0) - spins = HEAD_SPINS; - else if (spins < MAX_HEAD_SPINS) - spins <<= 1; - for (int k = spins;;) { // spin at head - long m, s, ns; - if ((m = (s = state) & ABITS) < RFULL ? - casState(s, ns = s + RUNIT) : - (m < WBIT && (ns = tryIncReaderOverflow(s)) != 0L)) { - WNode c; Thread w; - whead = node; - node.prev = null; - while ((c = node.cowait) != null) { - if (WCOWAIT.compareAndSet(node, c, c.cowait) && - (w = c.thread) != null) - LockSupport.unpark(w); - } - if (wasInterrupted) - Thread.currentThread().interrupt(); - return ns; - } - else if (m >= WBIT && --k <= 0) - break; - else - Thread.onSpinWait(); + // node is leader of a cowait group; almost same as acquireWrite + byte spins = 0, postSpins = 0; // retries upon unpark of first thread + boolean first = false; + Node pred = null; + for (long nextState;;) { + if (!first && (pred = node.prev) != null && + !(first = (head == pred))) { + if (pred.status < 0) { + cleanQueue(); // predecessor cancelled + continue; + } else if (pred.prev == null) { + Thread.onSpinWait(); // ensure serialization + continue; } } - else if (h != null) { - WNode c; Thread w; - while ((c = h.cowait) != null) { - if (WCOWAIT.compareAndSet(h, c, c.cowait) && - (w = c.thread) != null) - LockSupport.unpark(w); - } - } - if (whead == h) { - if ((np = node.prev) != p) { - if (np != null) - (p = np).next = node; // stale - } - else if ((ps = p.status) == 0) - WSTATUS.compareAndSet(p, 0, WAITING); - else if (ps == CANCELLED) { - if ((pp = p.prev) != null) { - node.prev = pp; - pp.next = node; - } + if ((first || pred == null) && + (nextState = tryAcquireRead()) != 0L) { + if (first) { + node.prev = null; + head = node; + pred.next = null; + node.waiter = null; } - else { - long time; - if (deadline == 0L) - time = 0L; - else if ((time = deadline - System.nanoTime()) <= 0L) - return cancelWaiter(node, node, false); - Thread wt = Thread.currentThread(); - node.thread = wt; - if (p.status < 0 && - (p != h || (state & ABITS) == WBIT) && - whead == h && node.prev == p) { - if (time == 0L) - LockSupport.park(this); - else - LockSupport.parkNanos(this, time); + signalCowaiters(node); + if (interrupted) + Thread.currentThread().interrupt(); + return nextState; + } else if (first && spins != 0) { + --spins; + Thread.onSpinWait(); + } else if (node.status == 0) { + if (node.waiter == null) + node.waiter = Thread.currentThread(); + node.status = WAITING; + } else { + long nanos; + spins = postSpins = (byte)((postSpins << 1) | 1); + if (!timed) + LockSupport.park(this); + else if ((nanos = time - System.nanoTime()) > 0L) + LockSupport.parkNanos(this, nanos); + else + break; + node.clearStatus(); + if ((interrupted |= Thread.interrupted()) && interruptible) + break; + } + } + return cancelAcquire(node, interrupted); + } + + // Cancellation support + + /** + * Possibly repeatedly traverses from tail, unsplicing cancelled + * nodes until none are found. Unparks nodes that may have been + * relinked to be next eligible acquirer. + */ + private void cleanQueue() { + for (;;) { // restart point + for (Node q = tail, s = null, p, n;;) { // (p, q, s) triples + if (q == null || (p = q.prev) == null) + return; // end of list + if (s == null ? tail != q : (s.prev != q || s.status < 0)) + break; // inconsistent + if (q.status < 0) { // cancelled + if ((s == null ? casTail(q, p) : s.casPrev(q, p)) && + q.prev == p) { + p.casNext(q, s); // OK if fails + if (p.prev == null) + signalNext(p); } - node.thread = null; - if (Thread.interrupted()) { - if (interruptible) - return cancelWaiter(node, node, true); - wasInterrupted = true; + break; + } + if ((n = p.next) != q) { // help finish + if (n != null && q.prev == p && q.status >= 0) { + p.casNext(n, q); + if (p.prev == null) + signalNext(p); + } + break; + } + s = q; + q = q.prev; + } + } + } + + /** + * If leader exists, possibly repeatedly traverses cowaiters, + * unsplicing the given cancelled node until not found. + */ + private void unlinkCowaiter(ReaderNode node, ReaderNode leader) { + if (leader != null) { + while (leader.prev != null && leader.status >= 0) { + for (ReaderNode p = leader, q; ; p = q) { + if ((q = p.cowaiters) == null) + return; + if (q == node) { + p.casCowaiters(q, q.cowaiters); + break; // recheck even if succeeded } } } @@ -1530,105 +1446,53 @@ /** * If node non-null, forces cancel status and unsplices it from - * queue if possible and wakes up any cowaiters (of the node, or - * group, as applicable), and in any case helps release current - * first waiter if lock is free. (Calling with null arguments - * serves as a conditional form of release, which is not currently - * needed but may be needed under possible future cancellation - * policies). This is a variant of cancellation methods in - * AbstractQueuedSynchronizer (see its detailed explanation in AQS - * internal documentation). + * queue, wakes up any cowaiters, and possibly wakes up successor + * to recheck status. * - * @param node if non-null, the waiter - * @param group either node or the group node is cowaiting with + * @param node the waiter (may be null if not yet enqueued) * @param interrupted if already interrupted * @return INTERRUPTED if interrupted or Thread.interrupted, else zero */ - private long cancelWaiter(WNode node, WNode group, boolean interrupted) { - if (node != null && group != null) { - Thread w; + private long cancelAcquire(Node node, boolean interrupted) { + if (node != null) { + node.waiter = null; node.status = CANCELLED; - // unsplice cancelled nodes from group - for (WNode p = group, q; (q = p.cowait) != null;) { - if (q.status == CANCELLED) { - WCOWAIT.compareAndSet(p, q, q.cowait); - p = group; // restart - } - else - p = q; - } - if (group == node) { - for (WNode r = group.cowait; r != null; r = r.cowait) { - if ((w = r.thread) != null) - LockSupport.unpark(w); // wake up uncancelled co-waiters - } - for (WNode pred = node.prev; pred != null; ) { // unsplice - WNode succ, pp; // find valid successor - while ((succ = node.next) == null || - succ.status == CANCELLED) { - WNode q = null; // find successor the slow way - for (WNode t = wtail; t != null && t != node; t = t.prev) - if (t.status != CANCELLED) - q = t; // don't link if succ cancelled - if (succ == q || // ensure accurate successor - WNEXT.compareAndSet(node, succ, succ = q)) { - if (succ == null && node == wtail) - WTAIL.compareAndSet(this, node, pred); - break; - } - } - if (pred.next == node) // unsplice pred link - WNEXT.compareAndSet(pred, node, succ); - if (succ != null && (w = succ.thread) != null) { - // wake up succ to observe new pred - succ.thread = null; - LockSupport.unpark(w); - } - if (pred.status != CANCELLED || (pp = pred.prev) == null) - break; - node.prev = pp; // repeat if new pred wrong/cancelled - WNEXT.compareAndSet(pp, pred, succ); - pred = pp; - } - } - } - WNode h; // Possibly release first waiter - while ((h = whead) != null) { - long s; WNode q; // similar to release() but check eligibility - if ((q = h.next) == null || q.status == CANCELLED) { - for (WNode t = wtail; t != null && t != h; t = t.prev) - if (t.status <= 0) - q = t; - } - if (h == whead) { - if (q != null && h.status == 0 && - ((s = state) & ABITS) != WBIT && // waiter is eligible - (s == 0L || q.mode == RMODE)) - release(h); - break; - } + cleanQueue(); + if (node instanceof ReaderNode) + signalCowaiters((ReaderNode)node); } return (interrupted || Thread.interrupted()) ? INTERRUPTED : 0L; } - // VarHandle mechanics - private static final VarHandle STATE; - private static final VarHandle WHEAD; - private static final VarHandle WTAIL; - private static final VarHandle WNEXT; - private static final VarHandle WSTATUS; - private static final VarHandle WCOWAIT; + /** + * If node non-null, forces cancel status and unsplices from + * leader's cowaiters list unless/until it is also cancelled. + * + * @param node if non-null, the waiter + * @param leader if non-null, the node heading cowaiters list + * @param interrupted if already interrupted + * @return INTERRUPTED if interrupted or Thread.interrupted, else zero + */ + private long cancelCowaiter(ReaderNode node, ReaderNode leader, + boolean interrupted) { + if (node != null) { + node.waiter = null; + node.status = CANCELLED; + unlinkCowaiter(node, leader); + } + return (interrupted || Thread.interrupted()) ? INTERRUPTED : 0L; + } + + // Unsafe + private static final Unsafe U = Unsafe.getUnsafe(); + private static final long STATE + = U.objectFieldOffset(StampedLock.class, "state"); + private static final long HEAD + = U.objectFieldOffset(StampedLock.class, "head"); + private static final long TAIL + = U.objectFieldOffset(StampedLock.class, "tail"); + static { - try { - MethodHandles.Lookup l = MethodHandles.lookup(); - STATE = l.findVarHandle(StampedLock.class, "state", long.class); - WHEAD = l.findVarHandle(StampedLock.class, "whead", WNode.class); - WTAIL = l.findVarHandle(StampedLock.class, "wtail", WNode.class); - WSTATUS = l.findVarHandle(WNode.class, "status", int.class); - WNEXT = l.findVarHandle(WNode.class, "next", WNode.class); - WCOWAIT = l.findVarHandle(WNode.class, "cowait", WNode.class); - } catch (ReflectiveOperationException e) { - throw new ExceptionInInitializerError(e); - } + Class ensureLoaded = LockSupport.class; } } diff --git a/test/jdk/java/util/concurrent/locks/Lock/CheckedLockLoops.java b/test/jdk/java/util/concurrent/locks/Lock/CheckedLockLoops.java --- a/test/jdk/java/util/concurrent/locks/Lock/CheckedLockLoops.java +++ b/test/jdk/java/util/concurrent/locks/Lock/CheckedLockLoops.java @@ -136,7 +136,7 @@ long time = timer.getTime(); long tpi = time / (iters * nthreads); System.out.print("\t" + LoopHelpers.rightJustify(tpi) + " ns per update"); - // double secs = (double)(time) / 1000000000.0; + // double secs = (double)time / 1000000000.0; // System.out.print("\t " + secs + "s run time"); System.out.println(); diff --git a/test/jdk/java/util/concurrent/locks/Lock/FlakyMutex.java b/test/jdk/java/util/concurrent/locks/Lock/FlakyMutex.java --- a/test/jdk/java/util/concurrent/locks/Lock/FlakyMutex.java +++ b/test/jdk/java/util/concurrent/locks/Lock/FlakyMutex.java @@ -49,31 +49,51 @@ static class MyRuntimeException extends RuntimeException {} static void checkThrowable(Throwable t) { - check((t instanceof MyError) || + if (!((t instanceof MyError) || (t instanceof MyException) || - (t instanceof MyRuntimeException)); + (t instanceof MyRuntimeException))) + unexpected(t); } static void realMain(String[] args) throws Throwable { - final int nThreads = 3; + final ThreadLocalRandom rndMain = ThreadLocalRandom.current(); + final int nCpus = Runtime.getRuntime().availableProcessors(); + final int maxThreads = Math.min(4, nCpus); + final int nThreads = rndMain.nextInt(1, maxThreads + 1); final int iterations = 10_000; final CyclicBarrier startingGate = new CyclicBarrier(nThreads); + final ExecutorService es = Executors.newFixedThreadPool(nThreads); final FlakyMutex mutex = new FlakyMutex(); - final ExecutorService es = Executors.newFixedThreadPool(nThreads); final Runnable task = () -> { try { + ThreadLocalRandom rnd = ThreadLocalRandom.current(); startingGate.await(); for (int i = 0; i < iterations; i++) { for (;;) { - try { mutex.lock(); break; } - catch (Throwable t) { checkThrowable(t); } + try { + if (rnd.nextBoolean()) + mutex.lock(); + else + mutex.lockInterruptibly(); + break; + } catch (Throwable t) { checkThrowable(t); } } - try { check(! mutex.tryLock()); } - catch (Throwable t) { checkThrowable(t); } + if (rnd.nextBoolean()) { + try { + check(! mutex.tryLock()); + } catch (Throwable t) { checkThrowable(t); } + } - try { check(! mutex.tryLock(1, TimeUnit.MICROSECONDS)); } - catch (Throwable t) { checkThrowable(t); } + if (rnd.nextInt(10) == 0) { + try { + check(! mutex.tryLock(1, TimeUnit.MICROSECONDS)); + } catch (Throwable t) { checkThrowable(t); } + } + + if (rnd.nextBoolean()) { + check(mutex.isLocked()); + } mutex.unlock(); } @@ -146,7 +166,11 @@ if (x == null ? y == null : x.equals(y)) pass(); else fail(x + " not equal to " + y);} public static void main(String[] args) throws Throwable { - try {realMain(args);} catch (Throwable t) {unexpected(t);} + int runsPerTest = Integer.getInteger("jsr166.runsPerTest", 1); + try { + for (int i = runsPerTest; i--> 0; ) + realMain(args); + } catch (Throwable t) { unexpected(t); } System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed); if (failed > 0) throw new AssertionError("Some tests failed");} @SuppressWarnings("unchecked") diff --git a/test/jdk/java/util/concurrent/locks/Lock/TimedAcquireLeak.java b/test/jdk/java/util/concurrent/locks/Lock/TimedAcquireLeak.java --- a/test/jdk/java/util/concurrent/locks/Lock/TimedAcquireLeak.java +++ b/test/jdk/java/util/concurrent/locks/Lock/TimedAcquireLeak.java @@ -42,6 +42,8 @@ import java.io.Reader; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; @@ -72,9 +74,9 @@ return new File(bin, programName).getPath(); } - static final String java = javaProgramPath("java"); - static final String jmap = javaProgramPath("jmap"); - static final String jps = javaProgramPath("jps"); + static final String javaPath = javaProgramPath("java"); + static final String jmapPath = javaProgramPath("jmap"); + static final String jpsPath = javaProgramPath("jps"); static String outputOf(Reader r) throws IOException { final StringBuilder sb = new StringBuilder(); @@ -159,7 +161,11 @@ static String match(String s, String regex, int group) { Matcher matcher = Pattern.compile(regex).matcher(s); - matcher.find(); + if (! matcher.find()) { + String msg = String.format( + "match failed: s=%s regex=%s", s, regex); + throw new AssertionError(msg); + } return matcher.group(group); } @@ -171,21 +177,20 @@ static int objectsInUse(final Process child, final String childPid, - final String className) { - final String regex = - "(?m)^ *[0-9]+: +([0-9]+) +[0-9]+ +\\Q"+className+"\\E(?:$| )"; - final Callable objectsInUse = - new Callable() { public Integer call() { - Integer i = Integer.parseInt( - match(commandOutputOf(jmap, "-histo:live", childPid), - regex, 1)); - if (i > 100) - System.out.print( - commandOutputOf(jmap, - "-dump:file=dump,format=b", - childPid)); - return i; - }}; + final String classNameRegex) { + String regex = + "(?m)^ *[0-9]+: +([0-9]+) +[0-9]+ +"+classNameRegex+"(?:$| )"; + Callable objectsInUse = () -> { + int i = Integer.parseInt( + match(commandOutputOf(jmapPath, "-histo:live", childPid), + regex, 1)); + if (i > 100) + System.out.print( + commandOutputOf(jmapPath, + "-dump:file=dump,format=b", + childPid)); + return i; + }; try { return rendezvousParent(child, objectsInUse); } catch (Throwable t) { unexpected(t); return -1; } } @@ -196,26 +201,27 @@ return; final String childClassName = Job.class.getName(); - final String classToCheckForLeaks = Job.classToCheckForLeaks(); - final String uniqueID = - String.valueOf(ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE)); + final String classNameRegex = Job.classNameRegexToCheckForLeaks(); + final String uniqueID = String.valueOf( + ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE)); - final String[] jobCmd = { - java, "-Xmx8m", "-XX:+UsePerfData", - "-classpath", System.getProperty("test.class.path"), - childClassName, uniqueID - }; + final ArrayList jobCmd = new ArrayList<>(); + Collections.addAll( + jobCmd, javaPath, "-Xmx8m", "-XX:+UsePerfData", + "-classpath", System.getProperty("test.class.path")); + Collections.addAll(jobCmd, Utils.getTestJavaOpts()); + Collections.addAll(jobCmd, childClassName, uniqueID); final Process p = new ProcessBuilder(jobCmd).start(); // Ensure subprocess jvm has started, so that jps can find it p.getInputStream().read(); sendByte(p.getOutputStream()); final String childPid = - match(commandOutputOf(jps, "-m"), + match(commandOutputOf(jpsPath, "-m"), "(?m)^ *([0-9]+) +\\Q"+childClassName+"\\E *"+uniqueID+"$", 1); - final int n0 = objectsInUse(p, childPid, classToCheckForLeaks); - final int n1 = objectsInUse(p, childPid, classToCheckForLeaks); + final int n0 = objectsInUse(p, childPid, classNameRegex); + final int n1 = objectsInUse(p, childPid, classNameRegex); equal(p.waitFor(), 0); equal(p.exitValue(), 0); failed += p.exitValue(); @@ -226,7 +232,7 @@ // implementation, and needing occasional adjustment. System.out.printf("%d -> %d%n", n0, n1); // Almost always n0 == n1 - // Maximum jitter observed in practice is 10 -> 17 + // Maximum jitter observed in practice is 7 check(Math.abs(n1 - n0) < 10); check(n1 < 25); drainers.shutdown(); @@ -244,9 +250,9 @@ // - in between calls to rendezvousChild, run code that may leak. //---------------------------------------------------------------- public static class Job { - static String classToCheckForLeaks() { + static String classNameRegexToCheckForLeaks() { return - "java.util.concurrent.locks.AbstractQueuedSynchronizer$Node"; + "\\Qjava.util.concurrent.locks.AbstractQueuedSynchronizer$\\E[A-Za-z]+"; } public static void main(String[] args) throws Throwable { diff --git a/test/jdk/java/util/concurrent/locks/ReentrantLock/CancelledLockLoops.java b/test/jdk/java/util/concurrent/locks/ReentrantLock/CancelledLockLoops.java --- a/test/jdk/java/util/concurrent/locks/ReentrantLock/CancelledLockLoops.java +++ b/test/jdk/java/util/concurrent/locks/ReentrantLock/CancelledLockLoops.java @@ -93,7 +93,7 @@ barrier.await(); if (print) { long time = timer.getTime(); - double secs = (double)(time) / 1000000000.0; + double secs = (double)time / 1000000000.0; System.out.println("\t " + secs + "s run time"); } diff --git a/test/jdk/java/util/concurrent/locks/ReentrantLock/LockOncePerThreadLoops.java b/test/jdk/java/util/concurrent/locks/ReentrantLock/LockOncePerThreadLoops.java --- a/test/jdk/java/util/concurrent/locks/ReentrantLock/LockOncePerThreadLoops.java +++ b/test/jdk/java/util/concurrent/locks/ReentrantLock/LockOncePerThreadLoops.java @@ -94,7 +94,7 @@ barrier.await(); if (print) { long time = timer.getTime(); - double secs = (double)(time) / 1000000000.0; + double secs = (double)time / 1000000000.0; System.out.println("\t " + secs + "s run time"); } diff --git a/test/jdk/java/util/concurrent/locks/ReentrantLock/SimpleReentrantLockLoops.java b/test/jdk/java/util/concurrent/locks/ReentrantLock/SimpleReentrantLockLoops.java --- a/test/jdk/java/util/concurrent/locks/ReentrantLock/SimpleReentrantLockLoops.java +++ b/test/jdk/java/util/concurrent/locks/ReentrantLock/SimpleReentrantLockLoops.java @@ -95,7 +95,7 @@ long time = timer.getTime(); long tpi = time / ((long)iters * nthreads); System.out.print("\t" + LoopHelpers.rightJustify(tpi) + " ns per lock"); - double secs = (double)(time) / 1000000000.0; + double secs = (double)time / 1000000000.0; System.out.println("\t " + secs + "s run time"); } diff --git a/test/jdk/java/util/concurrent/locks/ReentrantLock/TimeoutLockLoops.java b/test/jdk/java/util/concurrent/locks/ReentrantLock/TimeoutLockLoops.java --- a/test/jdk/java/util/concurrent/locks/ReentrantLock/TimeoutLockLoops.java +++ b/test/jdk/java/util/concurrent/locks/ReentrantLock/TimeoutLockLoops.java @@ -96,7 +96,7 @@ barrier.await(); if (print) { long time = timer.getTime(); - double secs = (double)(time) / 1000000000.0; + double secs = (double)time / 1000000000.0; System.out.println("\t " + secs + "s run time"); } diff --git a/test/jdk/java/util/concurrent/locks/ReentrantReadWriteLock/MapLoops.java b/test/jdk/java/util/concurrent/locks/ReentrantReadWriteLock/MapLoops.java --- a/test/jdk/java/util/concurrent/locks/ReentrantReadWriteLock/MapLoops.java +++ b/test/jdk/java/util/concurrent/locks/ReentrantReadWriteLock/MapLoops.java @@ -91,8 +91,8 @@ premove = Integer.parseInt(args[4]); // normalize probabilities wrt random number generator - removesPerMaxRandom = (int)(((double)premove/100.0 * 0x7FFFFFFFL)); - insertsPerMaxRandom = (int)(((double)pinsert/100.0 * 0x7FFFFFFFL)); + removesPerMaxRandom = (int)((double)premove/100.0 * 0x7FFFFFFFL); + insertsPerMaxRandom = (int)((double)pinsert/100.0 * 0x7FFFFFFFL); System.out.println("Using " + mapClass.getName()); @@ -125,7 +125,7 @@ long time = timer.getTime(); long tpo = time / (i * (long)nops); System.out.print(LoopHelpers.rightJustify(tpo) + " ns per op"); - double secs = (double)(time) / 1000000000.0; + double secs = (double)time / 1000000000.0; System.out.println("\t " + secs + "s run time"); map.clear(); }