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

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

自然语言处理学习笔记(三)————HanLP安装与使用
目录 1.HanLP安装 2.HanLP使用 (1)预下载 (2)测试 (3)命令行 (4)测试样例 3.pyhanlp可视化 4. HanLP词性表 1.HanLP安装 HanLP的 Python接口由 pyhanlp包提供,其安装…...

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 管理平台,旨在简化和统一在多个云提供商上运行 Kubernetes 集群的管理和操作。它是由阿里巴巴集团开发和维护的项目。 Mecha 的主要目标是提供一个统一的界面和工具,使用户能够更轻松地在不同的云提供商上…...
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多行数据合并为一行数据 实现查询效果原数据 方式一:MAX()数据效果SQL 方式二:LISTAGG()数据效果 方式三:WM_CONCAT()数据效果 实现查询效果 原数据 FZPROJECTVALUE1电脑$16001手机$121导管$12电脑$22手机$22 方式一:MAX…...

MySQL5.7 与 MariaDB10.1 审计插件兼容性验证
这是一篇关于发现 MariaDB 审计插件导致 MySQL 发生 crash 后,展开适配验证并进行故障处理的文章。 作者:官永强 爱可生DBA 团队成员,擅长 MySQL 运维方面的技能。热爱学习新知识,亦是个爱打游戏的宅男。 本文来源:原创…...
PyTorch Lightning教程五:Debug调试
如果遇到了这样一个问题,当一次训练模型花了好几天,结果突然在验证或测试的时候崩掉了,这个时候其实是很奔溃的,主要还是由于没有提前知道哪些时候会出现什么问题,本节会引入Lightning的Debug方案 1.fast_dev_run参数 …...
末流211无科研保研经验分享
文章目录 个人背景夏令营哈工大威海西工大光电北航软院北邮计算机中科大科学岛 预推免东南软件北航计算机 写在最后心路历程寄语 个人背景 院校:末流211专业背景:计算机科学与技术排名:夏令营7 / 126,预推免3 / 126英语ÿ…...
日期选择器多选换行
<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__分别是什么?0x03.原型链继承不同对象的原型链* 0x04.原型链污染原理0x05.merge()导致原型链污染0x06.ejs模板引擎RCEejs模板引擎另一处rce 0x07.jade模板引擎RCE【ctfs…...

18. SpringBoot 如何在 POM 中引入本地 JAR 包
❤️ 个人主页:水滴技术 🌸 订阅专栏:成功解决 BUG 合集 🚀 支持水滴:点赞👍 收藏⭐ 留言💬 Spring Boot 是一种基于 Spring 框架的轻量级应用程序开发框架,它提供了快速开发应用程…...

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

python自动收集粘贴板
win10的粘贴板可以用“winV”查看: 每次复制都相当于入栈一个字符串,粘贴相当于获取栈顶。 但是系统自带的这个粘贴板貌似不能一键导出,所以我写了个python代码完成这个功能: 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力扣做过了的题
链接: 剑指 Offer 30. 包含min函数的栈 题意: 如题 解: 初级算法里做过的题 优化是存储和min的差值使得只需要n的栈和一个int min 实际代码: #include<bits/stdc.h> using namespace std; class MinStack { public:…...

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

f12 CSS网页调试_css样式被划了黑线怎么办
我的问题是这样的 class加上去了,但是样式不生效,此时可能是样式被其他样式覆盖了, 解决方案就是 给颜色后边添加一个!important...
vue-制作自动滚动效果
第一步:下载 可以查看官方地址chenxuan0000 npm i vue-seamless-scroll -save 第二步:引用 import vueSeamlessScroll from "vue-seamless-scroll";//注册components: {vueSeamlessScroll,}, 第三步:使用 <vue-seamless…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...

python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...

定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...

uniapp微信小程序视频实时流+pc端预览方案
方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度WebSocket图片帧定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐RTMP推流TRTC/即构SDK推流❌ 付费方案 (部分有免费额度&#x…...

Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...

《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...