一、CountDownLatch的构造方法
// 创建倒数闩,设置倒数的总数State的值CountDownLatch doneSignal = new CountDownLatch(N);
二、countDown() 方法的作用
countDown() 方法的主要作用是将 CountDownLatch 的内部计数器减一。如果计数器减到零,则会唤醒所有等待的线程
三、countDown() 方法的源码分析
1、线程进入 countDown() 完成计数器减一(释放锁)的操作
public void countDown() {sync.releaseShared(1);}public final boolean releaseShared(int arg) {// 尝试释放共享锁if (tryReleaseShared(arg)) {// 释放锁成功则开始唤醒阻塞节点doReleaseShared();return true;}return false;}
2、更新 state 值,每调用一次,state 值减1,当 state -1 正好为 0 时,返回 true
protected boolean tryReleaseShared(int releases) {for (;;) {int c = getState();// 条件成立说明前面【已经有线程触发唤醒操作了,不需要再次触发】,这里返回 falseif (c == 0)return false;// 计数器减一int nextc = c-1;if (compareAndSetState(c, nextc))// 计数器为 0 时返回 truereturn nextc == 0;}}
i、获取当前状态:调用 getState() 获取当前计数器的值。
ii、检查计数器是否为零:如果计数器已经为零,则返回 false,表示说明前面已经有线程触发唤醒操作了,不需要再次触发。
iii、减少计数器:将计数器减一,得到新的计数器值 nextc。
vi、CAS 操作:使用 compareAndSetState(c, nextc) 尝试将计数器的值从 c 更新为 nextc。如果成功,则返回 nextc == 0,表示计数器是否减到零
3、当state = 0 时,当前线程需要执行唤醒阻塞节点的任务
private void doReleaseShared() {for (;;) {Node h = head;// 判断队列是否是空队列if (h != null && h != tail) {int ws = h.waitStatus;// 头节点的状态为 signal,说明后继节点需要被唤醒过if (ws == Node.SIGNAL) {// cas 设置头节点的状态为 0,设置失败继续自旋if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))continue;// 唤醒后继节点unparkSuccessor(h);}// 如果有其他线程已经设置了头节点的状态,重新设置为 PROPAGATE 传播属性else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))continue;}// 条件不成立说明被唤醒的节点非常积极,直接将自己设置为了新的head,// 此时唤醒它的节点(前驱)执行 h == head 不成立,所以不会跳出循环,会继续唤醒新的 head 节点的后继节点if (h == head)break;}}
i、检查头节点:获取等待队列的头节点 h
ii、检查头节点的状态:如果头节点的状态为 Node.SIGNAL,则表示需要唤醒后续节点。
iii、CAS 操作:使用 compareAndSetWaitStatus(h, Node.SIGNAL, 0) 尝试将头节点的状态从 Node.SIGNAL 更新为 0。如果成功,则调用 unparkSuccessor(h) 唤醒后续节点。
vi、检查头节点是否变化:如果头节点没有变化,则退出循环。
四、unparkSuccessor()方法的源码分析
unparkSuccessor() 是 Java 并发工具包(JUC)中 AbstractQueuedSynchronizer (AQS) 框架的核心方法之一。它的作用是唤醒等待队列中某个节点的后继节点(即下一个等待的线程)。
这个方法通常在线程释放锁或条件满足时调用,用于唤醒等待的线程
private void unparkSuccessor(Node node) {// 获取当前节点的等待状态int ws = node.waitStatus;if (ws < 0) // 如果状态为 SIGNAL 或 CONDITION,尝试将其重置为 0compareAndSetWaitStatus(node, ws, 0);// 获取当前节点的后继节点Node s = node.next;if (s == null || s.waitStatus > 0) { // 如果后继节点为空或已被取消s = null;// 从队列尾部向前遍历,找到最接近的有效节点for (Node t = tail; t != null && t != node; t = t.prev)if (t.waitStatus <= 0) // 找到未被取消的节点s = t;}if (s != null) // 如果找到有效节点,唤醒对应的线程LockSupport.unpark(s.thread);
}
源码逐行解析
1、获取当前节点的等待状态:
获取传入节点 node 的 waitStatus 状态。waitStatus 是 AQS 中 Node 类的一个字段,表示节点的状态,可能的值包括:
-
CANCELLED (1):节点已被取消
-
SIGNAL (-1):节点需要唤醒其后继节点
-
CONDITION (-2):节点在条件队列中等待
-
PROPAGATE (-3):共享模式下需要传播唤醒操作
2、重置节点的等待状态:
如果当前节点的 waitStatus 是负数(通常是 SIGNAL 或 CONDITION),则尝试通过 CAS 操作将其重置为 0。这一步是为了确保节点状态被正确清理
3、获取当前节点的后继节点:
获取当前节点的直接后继节点 s
4、获取当前节点的后继节点:
如果后继节点 s 为空或已被取消(waitStatus > 0),则需要从队列尾部向前遍历,找到最接近的有效节点。这是因为 AQS 的队列是一个双向链表,可能存在并发修改的情况,导致直接后继节点不可用。
-
从队列尾部 tail 开始向前遍历
-
找到第一个 waitStatus <= 0 的节点(即未被取消的节点)
-
将该节点赋值给 s
5、唤醒有效节点的线程:
如果找到有效节点 s,则调用 LockSupport.unpark(s.thread) 唤醒该节点对应的线程