当前位置: 首页 > 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…...

【力扣数据库知识手册笔记】索引

索引 索引的优缺点 优点1. 通过创建唯一性索引&#xff0c;可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度&#xff08;创建索引的主要原因&#xff09;。3. 可以加速表和表之间的连接&#xff0c;实现数据的参考完整性。4. 可以在查询过程中&#xff0c;…...

DAY 47

三、通道注意力 3.1 通道注意力的定义 # 新增&#xff1a;通道注意力模块&#xff08;SE模块&#xff09; class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...

Robots.txt 文件

什么是robots.txt&#xff1f; robots.txt 是一个位于网站根目录下的文本文件&#xff08;如&#xff1a;https://example.com/robots.txt&#xff09;&#xff0c;它用于指导网络爬虫&#xff08;如搜索引擎的蜘蛛程序&#xff09;如何抓取该网站的内容。这个文件遵循 Robots…...

CMake 从 GitHub 下载第三方库并使用

有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

关于 WASM:1. WASM 基础原理

一、WASM 简介 1.1 WebAssembly 是什么&#xff1f; WebAssembly&#xff08;WASM&#xff09; 是一种能在现代浏览器中高效运行的二进制指令格式&#xff0c;它不是传统的编程语言&#xff0c;而是一种 低级字节码格式&#xff0c;可由高级语言&#xff08;如 C、C、Rust&am…...

QT: `long long` 类型转换为 `QString` 2025.6.5

在 Qt 中&#xff0c;将 long long 类型转换为 QString 可以通过以下两种常用方法实现&#xff1a; 方法 1&#xff1a;使用 QString::number() 直接调用 QString 的静态方法 number()&#xff0c;将数值转换为字符串&#xff1a; long long value 1234567890123456789LL; …...

分布式增量爬虫实现方案

之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面&#xff0c;避免重复抓取&#xff0c;以节省资源和时间。 在分布式环境下&#xff0c;增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路&#xff1a;将增量判…...

Java 二维码

Java 二维码 **技术&#xff1a;**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...

rnn判断string中第一次出现a的下标

# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化

缓存架构 代码结构 代码详情 功能点&#xff1a; 多级缓存&#xff0c;先查本地缓存&#xff0c;再查Redis&#xff0c;最后才查数据库热点数据重建逻辑使用分布式锁&#xff0c;二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...