http://www.iocoder.cn/JUC/sike/aqs-2/#
在前面提到过,AQS 是构建 Java 同步组件的基础,我们期待它能够成为实现大部分同步需求的基础。
AQS 的设计模式采用的模板方法模式,子类通过继承的方式,实现它的抽象方法来管理同步状态。对于子类而言,它并没有太多的活要做,AQS 已经提供了大量的模板方法来实现同步,主要是分为三类:
自定义子类使用 AQS 提供的模板方法,就可以实现自己的同步语义。
独占式,同一时刻,仅有一个线程持有同步状态。
老艿艿:「1.1 独占式同步状态获取」 整个小节,是本文最难的部分。请一定保持耐心。
#acquire(int arg)
方法,为 AQS 提供的模板方法。该方法为独占式获取同步状态,但是该方法对中断不敏感。也就是说,由于线程获取同步状态失败而加入到 CLH 同步队列中,后续对该线程进行中断操作时,线程不会从 CLH 同步队列中移除。代码如下:
1: public final void acquire(int arg) { 2: if (!tryAcquire(arg) && 3: acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 4: selfInterrupt(); 5: } |
第 2 行:调用 #tryAcquire(int arg)
方法,去尝试获取同步状态,获取成功则设置锁状态并返回 true ,否则获取失败,返回 false 。若获取成功,#acquire(int arg)
方法,直接返回,不用线程阻塞,自旋直到获得同步状态成功。
#tryAcquire(int arg)
方法,需要自定义同步组件自己实现,该方法必须要保证线程安全的获取同步状态。代码如下:
protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); } |
第 3 行:如果 #tryAcquire(int arg)
方法返回 false ,即获取同步状态失败,则调用 #addWaiter(Node mode)
方法,将当前线程加入到 CLH 同步队列尾部。并且, mode
方法参数为 Node.EXCLUSIVE
,表示独占模式。
第 3 行:调用 boolean #acquireQueued(Node node, int arg)
方法,自旋直到获得同步状态成功。详细解析,见 「1.1.1 acquireQueued」 中。另外,该方法的返回值类型为 boolean
,当返回 true 时,表示在这个过程中,发生过线程中断。但是呢,这个方法又会清理线程中断的标识,所以在种情况下,需要调用【第 4 行】的 #selfInterrupt()
方法,恢复线程中断的标识,代码如下:
static void selfInterrupt() { Thread.currentThread().interrupt(); } |
boolean #acquireQueued(Node node, int arg)
方法,为一个自旋的过程,也就是说,当前线程(Node)进入同步队列后,就会进入一个自旋的过程,每个节点都会自省地观察,当条件满足,获取到同步状态后,就可以从这个自旋过程中退出,否则会一直执行下去。
流程图如下:
代码如下:
1: final boolean acquireQueued(final Node node, int arg) { 2: // 记录是否获取同步状态成功 3: boolean failed = true; 4: try { 5: // 记录过程中,是否发生线程中断 6: boolean interrupted = false; 7: /* 8: * 自旋过程,其实就是一个死循环而已 9: */ 10: for (;;) { 11: // 当前线程的前驱节点 12: final Node p = node.predecessor(); 13: // 当前线程的前驱节点是头结点,且同步状态成功 14: if (p == head && tryAcquire(arg)) { 15: setHead(node); 16: p.next = null; // help GC 17: failed = false; 18: return interrupted; 19: } 20: // 获取失败,线程等待--具体后面介绍 21: if (shouldParkAfterFailedAcquire(p, node) && 22: parkAndCheckInterrupt()) 23: interrupted = true; 24: } 25: } finally { 26: // 获取同步状态发生异常,取消获取。 27: if (failed) 28: cancelAcquire(node); 29: } 30: } |
failed
变量,记录是否获取同步状态成功。interrupted
变量,记录获取过程中,是否发生线程中断。Node#predecessor()
方法,获得当前线程的前一个节点 p
。p == head
代码块,若满足,则表示当前线程的前一个节点为头节点,因为 head
是最后一个获得同步状态成功的节点,此时调用 #tryAcquire(int arg)
方法,尝试获得同步状态。? 在 #acquire(int arg)
方法的【第 2 行】,也调用了这个方法。head
。p
不再指向下一个节点,让它自身更快的被 GC 。failed = false
,表示获取同步状态成功。#shouldParkAfterFailedAcquire(Node pre, Node node)
方法,判断获取失败后,是否当前线程需要阻塞等待。#cancelAcquire(Node node)
方法,取消获取同步状态。详细解析,见 「1.1.3 cancelAcquire」 。 1: private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { 2: // 获得前一个节点的等待状态 3: int ws = pred.waitStatus; 4: if (ws == Node.SIGNAL) // Node.SIGNAL 5: /* 6: * This node has already set status asking a release 7: * to signal it, so it can safely park. 8: */ 9: return true; 10: if (ws > 0) { // Node.CANCEL 11: /* 12: * Predecessor was cancelled. Skip over predecessors and 13: * indicate retry. 14: */ 15: do { 16: node.prev = pred = pred.prev; 17: } while (pred.waitStatus > 0); 18: pred.next = node; 19: } else { // 0 或者 Node.PROPAGATE 20: /* 21: * waitStatus must be 0 or PROPAGATE. Indicate that we 22: * need a signal, but don't park yet. Caller will need to 23: * retry to make sure it cannot acquire before parking. 24: */ 25: compareAndSetWaitStatus(pred, ws, Node.SIGNAL); 26: } 27: return false; 28: } |
pred
和 node
方法参数,传入时,要求前者必须是后者的前一个节点。pre
)的等待状态。下面会根据这个状态有三种情况的处理。Node.SIGNAL
时,表示 pred
的下一个节点 node
的线程需要阻塞等待。在 pred
的线程释放同步状态时,会对 node
的线程进行唤醒通知。所以,【第 9 行】返回 true ,表明当前线程可以被 park,安全的阻塞等待。0
或者 Node.PROPAGATE
时,通过 CAS 设置,将状态修改为 Node.SIGNAL
,即下一次重新执行 #shouldParkAfterFailedAcquire(Node pred, Node node)
方法时,满足【第 4 至 9 行】的条件。
Node.CONDITION
,因为它用在 ConditonObject 中。NODE.CANCELLED
时,则表明该线程的前一个节点已经等待超时或者被中断了,则需要从 CLH 队列中将该前一个节点删除掉,循环回溯,直到前一个节点状态 <= 0
。
1: private void cancelAcquire(Node node) { 2: // Ignore if node doesn't exist 3: if (node == null) 4: return; 5: 6: node.thread = null; 7: 8: // Skip cancelled predecessors 9: Node pred = node.prev; 10: while (pred.waitStatus > 0) 11: node.prev = pred = pred.prev; 12: 13: // predNext is the apparent node to unsplice. CASes below will 14: // fail if not, in which case, we lost race vs another cancel 15: // or signal, so no further action is necessary. 16: Node predNext = pred.next; 17: 18: // Can use unconditional write instead of CAS here. 19: // After this atomic step, other Nodes can skip past us. 20: // Before, we are free of interference from other threads. 21: node.waitStatus = Node.CANCELLED; 22: 23: // If we are the tail, remove ourselves. 24: if (node == tail && compareAndSetTail(node, pred)) { 25: compareAndSetNext(pred, predNext, null); 26: } else { 27: // If successor needs signal, try to set pred's next-link 28: // so it will get one. Otherwise wake it up to propagate. 29: int ws; 30: if (pred != head && 31: ((ws = pred.waitStatus) == Node.SIGNAL || 32: (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && 33: pred.thread != null) { 34: Node next = node.next; 35: if (next != null && next.waitStatus <= 0) 36: compareAndSetNext(pred, predNext, next); 37: } else { 38: unparkSuccessor(node); 39: } 40: 41: node.next = node; // help GC 42: } 43: } |
node
为空。node
节点的前一个节点 pred
。
#shouldParkAfterFailedAcquire(Node pred, Node node)
的【第 15 至 17 行】。pred
的下一个节点 predNext
。在这个变量上,有很“复杂”的英文,我们来理解下:predNext
从表面上看,和 node
是等价的。
#compareAndSetNext(...)
方法,使用 CAS 的方式,设置 pred
的下一个节点。pred
的下一个节点已经被其它线程设置成功。node
节点的为取消的等待状态 Node.CANCELLED
。在这个变量上,有很“复杂”的英文,我们再来理解下:
node
。Before, we are free of interference from other threads.
TODO 9000 芋艿,如何理解。pred
的新的下一个节点,一共分成三种情况。node
是尾节点,调用 #compareAndSetTail(...)
方法,CAS 设置 pred
为新的尾节点。
#compareAndSetNext(...)
方法,CAS 设置 pred
的下一个节点为空( null
)。pred
非首节点。pred
的等待状态为 Node.SIGNAL
,或者可被 CAS 为 Node.SIGNAL
。pred
的线程非空。
node
的 下一个节点 next
的等待状态非 Node.CANCELLED
,则调用 #compareAndSetNext(...)
方法,CAS 设置 pred
的下一个节点为 next
。pred
为首节点( ? 在【第 31 至 33 行】也会有别的情况 ),调用 #unparkSuccessor(Node node)
方法,唤醒 node
的下一个节点的线程等待。详细解析,见 《【死磕 Java 并发】—– J.U.C 之 AQS:阻塞和唤醒线程》 。
pred
为首节点,node
的下一个节点的阻塞等待,需要 node
释放同步状态时进行唤醒。但是,node
取消获取同步状态,则不会再出现 node
释放同步状态时进行唤醒 node
的下一个节点。因此,需要此处进行唤醒。第 41 行:TODO 芋艿 9002 为啥是 next 为 node 。目前收集到的资料如下:
next
的注释如下:
/** * 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. */ |
AQS 提供了acquire(int arg)
方法,以供独占式获取同步状态,但是该方法对中断不响应,对线程进行中断操作后,该线程会依然位于CLH同步队列中,等待着获取同步状态。为了响应中断,AQS 提供了 #acquireInterruptibly(int arg)
方法。该方法在等待获取同步状态时,如果当前线程被中断了,会立刻响应中断,并抛出 InterruptedException 异常。
public final void acquireInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (!tryAcquire(arg)) doAcquireInterruptibly(arg); } |
#tryAcquire(int arg)
方法,尝试获取同步状态,如果获取成功,则直接返回。#doAcquireInterruptibly(int arg)
方法,自旋直到获得同步状态成功,或线程中断抛出 InterruptedException 异常。 private void doAcquireInterruptibly(int arg) throws InterruptedException { final Node node = addWaiter(Node.EXCLUSIVE); boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) throw new InterruptedException(); // <1> } } finally { if (failed) cancelAcquire(node); } } |
它与 #acquire(int arg)
方法仅有两个差别:
interrupted
标志,而是直接抛出 InterruptedException 异常,即 <1>
处。AQS 除了提供上面两个方法外,还提供了一个增强版的方法 #tryAcquireNanos(int arg, long nanos)
。该方法为 #acquireInterruptibly(int arg)
方法的进一步增强,它除了响应中断外,还有超时控制。即如果当前线程没有在指定时间内获取同步状态,则会返回 false ,否则返回 true 。
流程图如下:
代码如下:
public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout); } |
#tryAcquire(int arg)
方法,尝试获取同步状态,如果获取成功,则直接返回。#tryAcquireNanos(int arg)
方法,自旋直到获得同步状态成功,或线程中断抛出 InterruptedException 异常,或超过指定时间返回获取同步状态失败。 static final long spinForTimeoutThreshold = 1000L; 1: private boolean doAcquireNanos(int arg, long nanosTimeout) 2: throws InterruptedException { 3: // nanosTimeout <= 0 4: if (nanosTimeout <= 0L) 5: return false; 6: // 超时时间 7: final long deadline = System.nanoTime() + nanosTimeout; 8: // 新增 Node 节点 9: final Node node = addWaiter(Node.EXCLUSIVE); 10: boolean failed = true; 11: try { 12: // 自旋 13: for (;;) { 14: final Node p = node.predecessor(); 15: // 获取同步状态成功 16: if (p == head && tryAcquire(arg)) { 17: setHead(node); 18: p.next = null; // help GC 19: failed = false; 20: return true; 21: } 22: /* 23: * 获取失败,做超时、中断判断 24: */ 25: // 重新计算需要休眠的时间 26: nanosTimeout = deadline - System.nanoTime(); 27: // 已经超时,返回false 28: if (nanosTimeout <= 0L) 29: return false; 30: // 如果没有超时,则等待nanosTimeout纳秒 31: // 注:该线程会直接从LockSupport.parkNanos中返回, 32: // LockSupport 为 J.U.C 提供的一个阻塞和唤醒的工具类,后面做详细介绍 33: if (shouldParkAfterFailedAcquire(p, node) && 34: nanosTimeout > spinForTimeoutThreshold) 35: LockSupport.parkNanos(this, nanosTimeout); 36: // 线程是否已经中断了 37: if (Thread.interrupted()) 38: throw new InterruptedException(); 39: } 40: } finally { 41: if (failed) 42: cancelAcquire(node); 43: } 44: } |
#doAcquireInterruptibly(int arg)
方法的基础上,做了超时控制的增强,所以相同部分,我们直接跳过。deadline
。nanosTimeout
。spinForTimeoutThreshold
,则调用 LockSupport#parkNanos(Object blocker, long nanos)
方法,休眠 nanosTimeout
纳秒。否则,就不需要休眠了,直接进入快速自旋的过程。原因在于,spinForTimeoutThreshold
已经非常小了,非常短的时间等待无法做到十分精确,如果这时再次进行超时等待,相反会让 nanosTimeout
的超时从整体上面表现得不是那么精确。所以,在超时非常短的场景中,AQS 会进行无条件的快速自旋。当线程获取同步状态后,执行完相应逻辑后,就需要释放同步状态。AQS 提供了#release(int arg)
方法,释放同步状态。代码如下:
1: public final boolean release(int arg) { 2: if (tryRelease(arg)) { 3: Node h = head; 4: if (h != null && h.waitStatus != 0) 5: unparkSuccessor(h); 6: return true; 7: } 8: return false; 9: } |
第 2 行:调用 #tryRelease(int arg)
方法,去尝试释放同步状态,释放成功则设置锁状态并返回 true ,否则获取失败,返回 false 。同时,它们分别对应【第 3 至 6】和【第 8 行】的逻辑。
#tryRelease(int arg)
方法,需要自定义同步组件自己实现,该方法必须要保证线程安全的释放同步状态。代码如下:
protected boolean tryRelease(int arg) { throw new UnsupportedOperationException(); } |
第 3 行:获得当前的 head
,避免并发问题。
第 4 行:头结点不为空,并且头结点状态不为 0 ( INITAL
未初始化)。为什么会出现 0 的情况呢?老艿艿的想法是,以 ReentrantReadWriteLock ( ? 内部基于 AQS 实现 ) 举例子:
线程 A 和线程 B ,基本同时释放锁,那么此时【第 3 行】h
可能获取到的是相同的 head
节点。此时,A 线程恰好先执行了 #unparkSuccessor(Node node)
方法,会将 waitStatus
设置为 0 。因此,#unparkSuccessor(Node node)
方法,对于 B 线程就不需要调用了。当然,更极端的情况下,可能 A 和 B 线程,都调用了 #unparkSuccessor(Node node)
方法。
老艿艿:如上是我的猜想,并未实际验证。如果不正确,或者有其他情况,欢迎斧正。
第 5 行:调用 #unparkSuccessor(Node node)
方法,唤醒下一个节点的线程等待。详细解析,见 《【死磕 Java 并发】—– J.U.C 之 AQS:阻塞和唤醒线程》 。
这里稍微总结下:
在 AQS 中维护着一个 FIFO 的同步队列。
- 当线程获取同步状态失败后,则会加入到这个 CLH 同步队列的对尾,并一直保持着自旋。
- 在 CLH 同步队列中的线程在自旋时,会判断其前驱节点是否为首节点,如果为首节点则不断尝试获取同步状态,获取成功则退出CLH同步队列。
- 当线程执行完逻辑后,会释放同步状态,释放后会唤醒其后继节点。
共享式与独占式的最主要区别在于,同一时刻:
例如,读操作可以有多个线程同时进行,而写操作同一时刻只能有一个线程进行写操作,其他操作都会被阻塞。参见 ReentrantReadWriteLock 。
AQS 提供 #acquireShared(int arg)
方法,共享式获取同步状态。代码如下:
#acquireShared(int arg)
方法,对标#acquire(int arg)
方法。
1: public final void acquireShared(int arg) { 2: if (tryAcquireShared(arg) < 0) 3: doAcquireShared(arg); 4: } |
第 2 行:调用 #tryAcquireShared(int arg)
方法,尝试获取同步状态,获取成功则设置锁状态并返回大于等于 0 ,否则获取失败,返回小于 0 。若获取成功,直接返回,不用线程阻塞,自旋直到获得同步状态成功。
#tryAcquireShared(int arg)
方法,需要自定义同步组件自己实现,该方法必须要保证线程安全的获取同步状态。代码如下:
protected int tryAcquireShared(int arg) { throw new UnsupportedOperationException(); } |
1: private void doAcquireShared(int arg) { 2: // 共享式节点 3: final Node node = addWaiter(Node.SHARED); 4: boolean failed = true; 5: try { 6: boolean interrupted = false; 7: for (;;) { 8: // 前驱节点 9: final Node p = node.predecessor(); 10: // 如果其前驱节点,获取同步状态 11: if (p == head) { 12: // 尝试获取同步 13: int r = tryAcquireShared(arg); 14: if (r >= 0) { 15: setHeadAndPropagate(node, r); 16: p.next = null; // help GC 17: if (interrupted) 18: selfInterrupt(); 19: failed = false; 20: return; 21: } 22: } 23: if (shouldParkAfterFailedAcquire(p, node) && 24: parkAndCheckInterrupt()) 25: interrupted = true; 26: } 27: } finally { 28: if (failed) 29: cancelAcquire(node); 30: } 31: } |
#acquireQueued(int arg)
方法的基础上,所以相同部分,我们直接跳过。#addWaiter(Node mode)
方法,将当前线程加入到 CLH 同步队列尾部。并且, mode
方法参数为 Node.SHARED
,表示共享模式。#tryAcquireShared(int arg)
方法,尝试获得同步状态。? 在 #acquireShared(int arg)
方法的【第 2 行】,也调用了这个方法。#setHeadAndPropagate(Node node, int propagate)
方法,设置新的首节点,并根据条件,唤醒下一个节点。详细解析,见 「2.1.2 setHeadAndPropagate」 。
#acquire(int arg)
方法,对于线程中断的处理方式相同,只是代码放置的位置不同。 1: private void setHeadAndPropagate(Node node, int propagate) { 2: Node h = head; // Record old head for check below 3: setHead(node); 4: /* 5: * Try to signal next queued node if: 6: * Propagation was indicated by caller, 7: * or was recorded (as h.waitStatus either before 8: * or after setHead) by a previous operation 9: * (note: this uses sign-check of waitStatus because 10: * PROPAGATE status may transition to SIGNAL.) 11: * and 12: * The next node is waiting in shared mode, 13: * or we don't know, because it appears null 14: * 15: * The conservatism in both of these checks may cause 16: * unnecessary wake-ups, but only when there are multiple 17: * racing acquires/releases, so most need signals now or soon 18: * anyway. 19: */ 20: if (propagate > 0 || h == null || h.waitStatus < 0 || 21: (h = head) == null || h.waitStatus < 0) { 22: Node s = node.next; 23: if (s == null || s.isShared()) 24: doReleaseShared(); 25: } 26: } |
h
。#setHead(Node node)
方法,设置 node
为新的首节点。propagate > 0
代码块,说明同步状态还能被其他线程获取。Node.PROPAGATE
或者 Node.SIGNAL
时,可以继续向下唤醒。Node#isShared()
方法,判断下一个节点为共享式获取同步状态。#doReleaseShared()
方法,唤醒后续的共享式获取同步状态的节点。详细解析,见 「2.1.2 setHeadAndPropagate」 。#acquireSharedInterruptibly(int arg)
方法,代码如下:
public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); } private void doAcquireSharedInterruptibly(int arg) throws InterruptedException { final Node node = addWaiter(Node.SHARED); boolean failed = true; 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 failed = false; return; } } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } } |
#tryAcquireSharedNanos(int arg, long nanosTimeout)
方法,代码如下:
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); return tryAcquireShared(arg) >= 0 || doAcquireSharedNanos(arg, nanosTimeout); } 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); boolean failed = true; 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 failed = false; return true; } } nanosTimeout = deadline - System.nanoTime(); if (nanosTimeout <= 0L) return false; if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold) LockSupport.parkNanos(this, nanosTimeout); if (Thread.interrupted()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } } |
当线程获取同步状态后,执行完相应逻辑后,就需要释放同步状态。AQS 提供了#releaseShared(int arg)
方法,释放同步状态。代码如下:
1: public final boolean releaseShared(int arg) { 2: if (tryReleaseShared(arg)) { 3: doReleaseShared(); 4: return true; 5: } 6: return false; 7: } |
第 2 行:调用 #tryReleaseShared(int arg)
方法,去尝试释放同步状态,释放成功则设置锁状态并返回 true ,否则获取失败,返回 false 。同时,它们分别对应【第 3 至 5】和【第 6 行】的逻辑。
#tryReleaseShared(int arg)
方法,需要自定义同步组件自己实现,该方法必须要保证线程安全的释放同步状态。代码如下:
protected boolean tryReleaseShared(int arg) { throw new UnsupportedOperationException(); } |
第 3 行:调用 #doReleaseShared()
方法,唤醒后续的共享式获取同步状态的节点。
1: private void doReleaseShared() { 2: /* 3: * Ensure that a release propagates, even if there are other 4: * in-progress acquires/releases. This proceeds in the usual 5: * way of trying to unparkSuccessor of head if it needs 6: * signal. But if it does not, status is set to PROPAGATE to 7: * ensure that upon release, propagation continues. 8: * Additionally, we must loop in case a new node is added 9: * while we are doing this. Also, unlike other uses of 10: * unparkSuccessor, we need to know if CAS to reset status 11: * fails, if so rechecking. 12: */ 13: for (;;) { 14: Node h = head; 15: if (h != null && h != tail) { 16: int ws = h.waitStatus; 17: if (ws == Node.SIGNAL) { 18: if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) 19: continue; // loop to recheck cases 20: unparkSuccessor(h); 21: } 22: else if (ws == 0 && 23: !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) 24: continue; // loop on failed CAS 25: } 26: if (h == head) // loop if head changed 27: break; 28: } 29: } |