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

news/2025/3/1 21:49:40/文章来源:https://www.cnblogs.com/jock766/p/18745479

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/891709.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

论文+1

1.2 研究目标本研究旨在解决微服务架构中质量属性冲突和评估方法缺失的问题。具体目标如下:构建可量化的质量属性评估指标体系:通过深入分析微服务架构的特点和业务需求,明确性能、可用性、安全性等关键质量属性的量化指标,为后续的评估和优化提供准确的数据支持。 设计基于…

2.28 AI课堂训练测试

老师要求我们用指令训练Ai完成我们开学测试的代码,锻炼我们的业务逻辑 开学测试要求:请设计一个仓储管理系统原型系统,该系统支持多个仓库的设立。统一设立物资台账,物资台账需包含物资编码、物资名称、规格、材质、供应商、品牌、物资分类,用户可以自定义物资的物资分类。…

Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!

Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来! 1. 优势介绍 Obsidian 是一款强大的本地知识管理软件,它像一个积木盒,让你用 Markdown 笔记搭建自己的知识宇宙。通过 双链笔记,你可以将不同的想法、灵感和信息连接起来,构建一个属于你自己的知识网络…

idea创建Spark项目报错记录

报错1:SparkException: A master URL must be set in your configuration解决方案:添加conf.setMaster("local"); local[“*”]中代表的是有几个线程,如果只写local就是单线程,如果是local[2]就是两个线程package com.fyq.spark.rdd;import org.apache.spark.Sp…

14.4.2 训练

首先来看看二元交叉熵的损失公式然后再来看看nn.functional.binary_cross_entropy_with_logits的用法然后来讲一下\(0.9352\)是怎么得出的(\(1.8462\)同理)每个样本没有归一化的输出为[1.1, -2.2, 3.3, -4.4],标签分别为[1.0, 0.0, 0.0, 0.0] 将这四个样本代入最开始给的二元…

【Nacos】源码以及启动环境搭建

1 前言 Nacos 现在越来越成为微服务结构中不可或缺的一部分,他的集成服务以及配置的管理一体化、简单方便也是我们手首选的原因,那么了解他的原理就很重要。所以我们这节拉取下源码调试下环境,方便我们观察服务注册的整体过程,以及配置下发等核心逻辑的过程,有助于更好的…

foobar2000 v2.24.2 汉化版

foobar2000 v2.24.2 汉化版 -----------------------【软件截图】---------------------- -----------------------【软件介绍】---------------------- foobar2000 是一个 Windows 平台下的高级音频播放器.包含完全支持 unicode 及支持播放增益的高级标签功能. 特色: * 支持的…

Jsmoke-一款强大的js检测工具,浏览器部署即用,使用方便且高效

Jsmoke 🚬🚬 by Yn8rt ​ 该插件由 Yn8rt师傅 开发,插件可以理解为主动版的hae和apifinder,因为其中的大多数规则我都引用了,当你认为当前页面,以及其调用的js文件存在敏感信息的时候,可以用它来帮你打开突破口,速度很快,非常方便,也比较直观,该插件用于检…