当前位置: 首页 > news >正文

Java并发系列之六:CountDownLatch

CountDownLatch作为开发中最常用的组件,今天我们来聊聊它的作用以及内部构造。

首先尝试用一句话对CountDownLatch进行概括: CountDownLatch基于AQS,它实现了闩锁,在开发中可以将其用作任务计数器。

若想要较为系统地去理解这些特性,我觉得最好的方式就是通过源码,在一览源码之后自己再动手实践一遍,这样就能够做到知其然并知其所以然。

如果你从来没有接触过CountDownLatch,那么可能会好奇,到底什么是闩锁(Latch) ?这个组件又有什么用?在源码的开头,可以看到这么一行注释:

A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes."

简单直译过来就是: CountDownLatch这种同步工具允许一条或多条线程等待其他线程中的一组操作完成后,再继续执行。

例子

好像还是有点拗口,举个简单的例子来辅助理解一下。

比如我需要收集七颗龙珠才能召唤神龙,这七颗龙珠没有相互依赖关系。如果一颗一颗收集太慢了,那么更好的方式就是派出七个人,每个人都帮我去收集其中-颗,这样效率就能够大大提升。从编程的角度来说,这里需要建立七个工作线程分别同时去寻找指定编号的龙珠,然后主线程来完成召唤神龙的操作。

但这里就存在一个问题,因为每条工作线程找到相应龙珠需要花费的时间不一样,那么主线程需要等待多久?怎么才能得到“所有龙珠都已经被工作线程找到了的这个信息呢?

这里就是CountDownLatch这个同步工具的用武之地,正如它的介绍那样,它能够帮助主线程在等待其他工作线程里的任务完成之后,再继续执行。

接下来,我想从源码角度探寻一下,为什么CountDownLatch这么简单易用,它的下层是如何设计的,能不能从中学习一些思想。

尝试设计

在看源码之前,我们可以先简单思考一下,因为我们已经熟悉了AQS,在此基础上,如果让你来设计,你会尝试怎么做?

我们需要解决的问题:多个线程等待多个线程中任务的完成。为了便于表述,这里就简单称为主线程等待子线程。

既然主线程在等待,那么它就应该进入等待队列,那么它被唤醒的条件是什么?当然是子任务都完成的时候,AQS中state这个int值我觉得可以用来表示正在等待执行执行完成的任务数,每当一个任务完成就,state值就自减1,当state为0时,唤醒正在等待的主线程。

这样的大致思路,看上去没什么大问题,事实上CountDownLatch确实也是结合AQS这么做的,关键在于细节,细节是魔鬼,因为在并发场景下往往差之毫厘,谬以千里。

属性

private final Sync sync;

和ReentrantLock一样,CountDownLatch只有一个类型为内部类Sync的成员属性sync,sync被final修饰,意味着一旦初始化,就不可修改引用了。那么它的初始化时机是什么时候?

在CountDownLatch的唯一构造函数中:

public CountDownLatch(int count) {if (count < 0) throw new IllegalAr gumentException("count < 0");this.sync = new Sync(count) ;
}

其中count这个参数代表子任务的个数。

内部类

private static final class Sync extends AbstractQueuedSynchronizer {private static final long ser ialVersionUID = 4982264981922014374L;Sync(int count) {setState (count) ;}int getCount() {return getState();}protected int tryAcqui reShared(int acquires) {return (getState() == 0) ? 1 : -1;}protected boolean tryReleaseShared(int releases) {// Decrement count; signal when transition to zerofor (;;) {int C = getState();if(c==0)return false;int nextc = c-1;if (compareAndSetState(c,nextc))return nextc == 0;}}
}

首先看到Sync继承了AQS,那么它就拥有了AQS的所有能力,再来类中的几个方法。

Sync(int count)

构造函数,参数count即AQS的state值,代表子任务的个数。

int getCount()

获取当前需要等待的任务数。

protected int tryAcquireShared(int acquires)

这个方法是对AQS内部方法的重写,方法名被shared修饰,说明用到了共享模式。

这里简单介绍一下AQS的共享模式,远离上和独占模式差别不大。主要从两个方面去理解:

1. state

独占模式下,当state为1, 代表锁正在被占用,其他想要获取锁的线程必须等待。共享模式下,state 值可以被多个线程同时修改,增加1代表当前线程获取共享锁,减去1代表当前线程释放共享锁。

2. FIFO队列

独占模式下,只有即将出队的Node中的线程被唤醒。共享模式下,除了即将出队的Node中的线程被唤醒,也会唤醒后续处于共享模式下Node中的线程。

回到方法本身,tryAcquireShared内部逻辑就是,当state为0,代表子任务全部完成,那么返回1,否则返回-1。

如果返回值是负数,则代表尝试获取锁失败,有需要的话可以进入队列等待;如果返回值是0,则代表尝试获取共享锁成功,即使后续节点也在等待共享锁,不需要唤醒后续节点;如果返回值是1,则代表尝试获取锁成功,并唤醒后续节点,前提是它也在等待共享锁。

protected boolean tryReleaseShared(int releases)

内部是一个自旋操作,当state为0, 代表子任务已经全部完成,不需要释放锁,则返回false。否则,使用CAS将state值自减1,直至state为0,说明锁已被完全释放,才返回true。

看到这边呢,有的读者可能依然一头雾水,内部类Sync中方法的逻辑,目前来看似乎和我们最初的需求没什么太直接的关联。

我们先带着这个问题再来看看公有方法的实现。

方法

public void await() throws Inter ruptedException {sync.acquireSharedInterruptibly(1);
}

当主线程调用await时,实际上内部调用的是AQS的acquireSharedInterruptibly方法。

public final void acqui reSharedInterruptibly(int arg) throws Inter ruptedException {if (Thread. interrupted())throw new InterruptedException();if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);
}

而acquireSharedInterruptibly内部首先判断tryAcquireShared的返回值,如果为负数,那么再执行doAcquireSharedInterruptibly的逻辑。

我们可以这么理解:若子任务已经全部全部完成,那么await直接返回。若还存在子任务没有完成,则调用doAcquireSharedInterruptibly方法,而doAcquireSharedInterruptibly内部主要逻辑则是初始化一个封装主线程的Node节点,该节点进入AQS的FIFO队列,并等待子任务的全部完成。

逻辑细节来看doAcquireSharedInterruptibly方法的源码:

private void doAcqui reSharedInterruptibly(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 = tryAcqui reShared(arg);if (r>= 0) {setHeadAndPropagate(node, r);p.next = null; // help GCfailed = false;return;}}if (shouldParkAfterFailedAcquire(p,node) &&parkAndCheckInterrupt())throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}
}

在方法的第2行将当前线程封装为Node插入FIFO队列队尾。后续逻辑和AQS独占模式类似,在这里可以看到第9-10行,当tryAcquireShared返回值为正数,说明子任务已经全部完成,此时方法将会return,调用await的主线程将会返回。

这样,从最上层的await一层层往下分析,就能理解其中的调用逻辑了。我觉得AQS是一个抽象程度比较高的框架,CountDownLatch是利用这种抽象实现了一种具体的功能。

public void countDown() {sync.releaseShared(1);
}

当工作线程调用countDown方法时,内部调用的是AQS的releaseShared方法。

public final boolean releaseShared(int arg) {if (tryReleaseShared(arg)) {doReleaseShared() ; return true;}return false;
}

大致逻辑是,如果tryReleaseShared返回值为true,则调用doReleaseShared方法,

doReleaseShared内部的大致逻辑就是唤醒后续等待的Node。

所以我们可以这么理解:当工作线程调用countDown时,若任务还没有全部完成,则直接返回;若当前任务全部完成,那么就唤醒FIFO队列中正在等待的Node,其实也就是主线程节点。

因为从CountDownLatch到AQS的调用链比较长,我这里画了一张时序图来辅助大家理解。

图中浅黄色部分为CountDownLatch的方法调用,浅绿色为AQS,浅蓝色为内部类SynC。这里模拟了在主线程中提交两个子任务。

相关文章:

Java并发系列之六:CountDownLatch

CountDownLatch作为开发中最常用的组件&#xff0c;今天我们来聊聊它的作用以及内部构造。 首先尝试用一句话对CountDownLatch进行概括: CountDownLatch基于AQS&#xff0c;它实现了闩锁&#xff0c;在开发中可以将其用作任务计数器。 若想要较为系统地去理解这些特性&#xff…...

24数据结构-图的基本概念与存储结构

目录 第六章 图6.1 图的基本概念知识回顾 6.2 图的储存结构&#xff08;邻接矩阵法&#xff09;1. 数组表示法(1) 有向图&#xff0c;无向图的邻接矩阵 2. 定义邻接矩阵的结构3. 定义图的结构4. 构造图G5. 特点 第六章 图 6.1 图的基本概念 图是一种非线性结构 图的特点&am…...

自然语言处理学习笔记(三)————HanLP安装与使用

目录 1.HanLP安装 2.HanLP使用 &#xff08;1&#xff09;预下载 &#xff08;2&#xff09;测试 &#xff08;3&#xff09;命令行 &#xff08;4&#xff09;测试样例 3.pyhanlp可视化 4. HanLP词性表 1.HanLP安装 HanLP的 Python接口由 pyhanlp包提供&#xff0c;其安装…...

CS 144 Lab Five -- the network interface

CS 144 Lab Five -- the network interface TCP报文的数据传输方式地址解析协议 ARPARP攻击科普 Network Interface 具体实现测试tcp_ip_ethernet.ccTCPOverIPv4OverEthernetAdapterTCPOverIPv4OverEthernetSpongeSocket通信过程 对应课程视频: 【计算机网络】 斯坦福大学CS144…...

Mecha

一、Mecha Mecha 是一个开源的多云 Kubernetes 管理平台&#xff0c;旨在简化和统一在多个云提供商上运行 Kubernetes 集群的管理和操作。它是由阿里巴巴集团开发和维护的项目。 Mecha 的主要目标是提供一个统一的界面和工具&#xff0c;使用户能够更轻松地在不同的云提供商上…...

Apache RocketMQ之集成RocketMQ_MQTT 安装部署协议

Apache RocketMQ 安装说明 安装步骤 参考快速开始 https://rocketmq.apache.org/zh/docs/quickStart/01quickstart 安装可视化rocketmq_dashboard下载地址 https://rocketmq.apache.org/zh/docs/4.x/deployment/03Dashboard/ 安装rocketmq_mqtt https://rocketmq.apache.o…...

Oracle多行数据合并为一行数据,并将列数据转为字段名

Oracle多行数据合并为一行数据 实现查询效果原数据 方式一&#xff1a;MAX()数据效果SQL 方式二&#xff1a;LISTAGG()数据效果 方式三&#xff1a;WM_CONCAT()数据效果 实现查询效果 原数据 FZPROJECTVALUE1电脑$16001手机$121导管$12电脑$22手机$22 方式一&#xff1a;MAX…...

MySQL5.7 与 MariaDB10.1 审计插件兼容性验证

这是一篇关于发现 MariaDB 审计插件导致 MySQL 发生 crash 后&#xff0c;展开适配验证并进行故障处理的文章。 作者&#xff1a;官永强 爱可生DBA 团队成员&#xff0c;擅长 MySQL 运维方面的技能。热爱学习新知识&#xff0c;亦是个爱打游戏的宅男。 本文来源&#xff1a;原创…...

PyTorch Lightning教程五:Debug调试

如果遇到了这样一个问题&#xff0c;当一次训练模型花了好几天&#xff0c;结果突然在验证或测试的时候崩掉了&#xff0c;这个时候其实是很奔溃的&#xff0c;主要还是由于没有提前知道哪些时候会出现什么问题&#xff0c;本节会引入Lightning的Debug方案 1.fast_dev_run参数 …...

末流211无科研保研经验分享

文章目录 个人背景夏令营哈工大威海西工大光电北航软院北邮计算机中科大科学岛 预推免东南软件北航计算机 写在最后心路历程寄语 个人背景 院校&#xff1a;末流211专业背景&#xff1a;计算机科学与技术排名&#xff1a;夏令营7 / 126&#xff0c;预推免3 / 126英语&#xff…...

日期选择器多选换行

<el-form-item label"日期选择"><div class"multi-date-picker"><div class"date-item"><span class"dateIcon"><el-icon><Calendar /></el-icon></span><span class"dateIt…...

NodeJS原型链污染ctfshow_nodejs

文章目录 NodeJS原型链污染&ctfshow_nodejs前言0x01.原型与原型链0x02.prototype和__proto__分别是什么&#xff1f;0x03.原型链继承不同对象的原型链* 0x04.原型链污染原理0x05.merge()导致原型链污染0x06.ejs模板引擎RCEejs模板引擎另一处rce 0x07.jade模板引擎RCE【ctfs…...

18. SpringBoot 如何在 POM 中引入本地 JAR 包

❤️ 个人主页&#xff1a;水滴技术 &#x1f338; 订阅专栏&#xff1a;成功解决 BUG 合集 &#x1f680; 支持水滴&#xff1a;点赞&#x1f44d; 收藏⭐ 留言&#x1f4ac; Spring Boot 是一种基于 Spring 框架的轻量级应用程序开发框架&#xff0c;它提供了快速开发应用程…...

vue2-$nextTick有什么作用?

1、$nextTick是什么&#xff1f; 官方定义&#xff1a;在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法&#xff0c;获取更新后的DOM。 解释&#xff1a;Vue在更新DOM时是异步执行的&#xff0c;当数据发生变化时&#xff0c;Vue将开启一个异步更新的队…...

python自动收集粘贴板

win10的粘贴板可以用“winV”查看&#xff1a; 每次复制都相当于入栈一个字符串&#xff0c;粘贴相当于获取栈顶。 但是系统自带的这个粘贴板貌似不能一键导出&#xff0c;所以我写了个python代码完成这个功能&#xff1a; import pyperclip import timetmp while True:txt…...

Vue3_语法糖—— <script setup>以及unplugin-auto-import自动引入插件

<script setup>import { ref , onMounted} from vue;let obj ref({a: 1,b: 2,}); let changeObj ()>{console.log(obj)obj.value.c 3 //ref写法}onMounted(()>{console.log(obj)})</script> 里面的代码会被编译成组件 setup() 函数的内容。 相当于 <…...

2023-08-06力扣做过了的题

链接&#xff1a; 剑指 Offer 30. 包含min函数的栈 题意&#xff1a; 如题 解&#xff1a; 初级算法里做过的题 优化是存储和min的差值使得只需要n的栈和一个int min 实际代码&#xff1a; #include<bits/stdc.h> using namespace std; class MinStack { public:…...

进程间通信之管道

文章目录 一、管道1. 匿名管道2. 命名管道 进程具有独立性&#xff0c;因此进程间通信的前提是两个进程能看到同一份资源 一、管道 对于进程打开的内存文件&#xff0c;操作系统是以引用计数的方式创建的 file 结构体&#xff0c;如果让两个进程与同一个 file 结构体关联&…...

f12 CSS网页调试_css样式被划了黑线怎么办

我的问题是这样的 class加上去了,但是样式不生效,此时可能是样式被其他样式覆盖了, 解决方案就是 给颜色后边添加一个!important...

vue-制作自动滚动效果

第一步&#xff1a;下载 可以查看官方地址chenxuan0000 npm i vue-seamless-scroll -save 第二步&#xff1a;引用 import vueSeamlessScroll from "vue-seamless-scroll";//注册components: {vueSeamlessScroll,}, 第三步&#xff1a;使用 <vue-seamless…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战

前言 现在我们有个如下的需求&#xff0c;设计一个邮件发奖的小系统&#xff0c; 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式&#xff08;Decorator Pattern&#xff09;允许向一个现有的对象添加新的功能&#xff0c;同时又不改变其…...

vscode(仍待补充)

写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh&#xff1f; debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具

文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

oracle与MySQL数据库之间数据同步的技术要点

Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异&#xff0c;它们的数据同步要求既要保持数据的准确性和一致性&#xff0c;又要处理好性能问题。以下是一些主要的技术要点&#xff1a; 数据结构差异 数据类型差异&#xff…...

企业如何增强终端安全?

在数字化转型加速的今天&#xff0c;企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机&#xff0c;到工厂里的物联网设备、智能传感器&#xff0c;这些终端构成了企业与外部世界连接的 “神经末梢”。然而&#xff0c;随着远程办公的常态化和设备接入的爆炸式…...

Linux --进程控制

本文从以下五个方面来初步认识进程控制&#xff1a; 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程&#xff0c;创建出来的进程就是子进程&#xff0c;原来的进程为父进程。…...

【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习

禁止商业或二改转载&#xff0c;仅供自学使用&#xff0c;侵权必究&#xff0c;如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...

初探Service服务发现机制

1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能&#xff1a;服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源&#xf…...

libfmt: 现代C++的格式化工具库介绍与酷炫功能

libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库&#xff0c;提供了高效、安全的文本格式化功能&#xff0c;是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全&#xff1a…...

什么是VR全景技术

VR全景技术&#xff0c;全称为虚拟现实全景技术&#xff0c;是通过计算机图像模拟生成三维空间中的虚拟世界&#xff0c;使用户能够在该虚拟世界中进行全方位、无死角的观察和交互的技术。VR全景技术模拟人在真实空间中的视觉体验&#xff0c;结合图文、3D、音视频等多媒体元素…...