一、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) 唤醒该节点对应的线程

CountDownLatch的countDown()方法的底层源码的更多相关文章

  1. Android开发之漫漫长途 Ⅵ——图解Android事件分发机制(深入底层源码)

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  2. 为什么很多类甚者底层源码要implements Serializable ?

    为什么很多类甚者底层源码要implements Serializable ? 在碰到异常类RuntimeException时,发现Throwable实现了 Serializable,还有我们平进的ja ...

  3. List-LinkedList、set集合基础增强底层源码分析

    List-LinkedList 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 继上一章继续讲解,上章内容: List-ArreyLlist集合基础增强底层源码分析:https:// ...

  4. 从底层源码浅析Mybatis的SqlSessionFactory初始化过程

    目录 搭建源码环境 POM依赖 测试SQL Mybatis全局配置文件 UserMapper接口 UserMapper配置 User实体 Main方法 快速进入Debug跟踪 源码分析准备 源码分析 ...

  5. Java泛型底层源码解析-ArrayList,LinkedList,HashSet和HashMap

    声明:以下源代码使用的都是基于JDK1.8_112版本 1. ArrayList源码解析 <1. 集合中存放的依然是对象的引用而不是对象本身,且无法放置原生数据类型,我们需要使用原生数据类型的包 ...

  6. 2018.11.20 Struts2中对结果处理方式分析&struts2内置的方式底层源码剖析

    介绍一下struts2内置帮我们封装好的处理结果方式也就是底层源码分析 这是我们的jar包里面找的位置目录 打开往下拉看到result-type节点 name那一列就是我们的type类型取值 上一篇博 ...

  7. 总结HashSet以及分析部分底层源码

    总结HashSet以及分析部分底层源码 1. HashSet继承的抽象类和实现的接口 继承的抽象类:AbstractSet 实现了Set接口 实现了Cloneable接口 实现了Serializabl ...

  8. LInkedList总结及部分底层源码分析

    LInkedList总结及部分底层源码分析 1. LinkedList的实现与继承关系 继承:AbstractSequentialList 抽象类 实现:List 接口 实现:Deque 接口 实现: ...

  9. Vector总结及部分底层源码分析

    Vector总结及部分底层源码分析 1. Vector继承的抽象类和实现的接口 Vector类实现的接口 List接口:里面定义了List集合的基本接口,Vector进行了实现 RandomAcces ...

  10. 深入理解Whitelabel Error Page底层源码

    深入理解Whitelabel Error Page底层源码 (一)服务器请求处理错误则转发请求url StandardHostValve的invoke()方法将根据请求的url选择正确的Context ...

随机推荐

  1. biancheng-Linux教程

    目录http://c.biancheng.net/linux_tutorial/ 1Linux简介2Linux安装3Linux文件和目录管理4Linux打包(归档)和压缩5Vim文本编辑器6Linux ...

  2. try-catch-finally的使用

    /* * 一.异常的处理:抓抛模型 * * 过程一:"抛":程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象. * 并将此对象抛出. * 一旦抛出对象 ...

  3. C# 深度学习框架 TorchSharp 原生训练模型和图像识别-手写数字识别

    目录 开始使用 Torch 准备 下载数据集 显示图片 加载数据集 定义网络 优化模型参数 训练模型 保存和加载模型 使用模型识别图片 教程名称:使用 C# 入门深度学习 作者:痴者工良 教程地址:h ...

  4. EIP和NAT结合如何实现统一公网出口IP

    本文分享自天翼云开发者社区<EIP和NAT结合如何实现统一公网出口IP>,作者:j****n 通过EIP和NAT网关结合,为已绑定EIP的ECS实例统一公网出口IP,有利于更高效的管理互联 ...

  5. Doris建明细测试表,导入测试数据

    分区.分桶.明细表 测试用户表 CREATE TABLE user_info( dt DATE, id varchar(36) NOT NULL DEFAULT '10', user_name var ...

  6. 中国最难入职的IT公司排行榜

    在IT行业竞争日益白热化的今天,头部企业的招聘门槛不断刷新求职者的认知.根据最新行业调研和招聘数据,我们整理出2025年中国最难入职的几家互联网公司,并揭秘其背后严苛的选拔逻辑. 通常衡量难不难,会从 ...

  7. 飞牛fnOs安装autman奥特曼机器人喂饭教程

    前言 简介:autman奥特曼机器人是干什么的? autMan是机器人牵引的扩展性极强的一站式解决方案 原生支持对接qq框架.qq频道.微信框架(酷V西瓜可爱猫千寻鲲鹏).内置微信.微信客服.公众号. ...

  8. Github Copilot的使用方法和快捷键

    GitHub Copilot是一款由GitHub和OpenAI共同开发的代码智能补全工具,它使用机器学习模型来为你提供代码建议和自动完成,可以加快开发过程并提高代码质量.下面是使用GitHub Cop ...

  9. 附039.Kubernetes_v1.32.2高可用部署架构二

    部署组件 该 Kubernetes 部署过程中,对于部署环节,涉及多个组件,主要有 kubeadm .kubelet .kubectl. kubeadm介绍 Kubeadm 为构建 Kubernete ...

  10. Flink - [05] 时间语义 & Watermark

    题记部分 一.时间语义 Flink中的时间语义分为以下, (1)Event Time:事件创建的时间 (2)Ingestion Time:数据进入Flink的时间 (3)Processing Time ...