CountDownLatch的countDown()方法的底层源码

一、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();
            // 条件成立说明前面【已经有线程触发唤醒操作了,不需要再次触发】,这里返回 false
            if (c == 0)
                return false;
            // 计数器减一
            int nextc = c-1;
            if (compareAndSetState(c, nextc))
                // 计数器为 0 时返回 true
                return 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,尝试将其重置为 0
        compareAndSetWaitStatus(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) 唤醒该节点对应的线程

posted @ 2025-03-01 21:46  jock_javaEE  阅读(87)  评论(0)    收藏  举报