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

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…...

抖音增长新引擎:品融电商,一站式全案代运营领跑者

抖音增长新引擎&#xff1a;品融电商&#xff0c;一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中&#xff0c;品牌如何破浪前行&#xff1f;自建团队成本高、效果难控&#xff1b;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...

cf2117E

原题链接&#xff1a;https://codeforces.com/contest/2117/problem/E 题目背景&#xff1a; 给定两个数组a,b&#xff0c;可以执行多次以下操作&#xff1a;选择 i (1 < i < n - 1)&#xff0c;并设置 或&#xff0c;也可以在执行上述操作前执行一次删除任意 和 。求…...

spring:实例工厂方法获取bean

spring处理使用静态工厂方法获取bean实例&#xff0c;也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下&#xff1a; 定义实例工厂类&#xff08;Java代码&#xff09;&#xff0c;定义实例工厂&#xff08;xml&#xff09;&#xff0c;定义调用实例工厂&#xff…...

vue3 定时器-定义全局方法 vue+ts

1.创建ts文件 路径&#xff1a;src/utils/timer.ts 完整代码&#xff1a; import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...

12.找到字符串中所有字母异位词

&#x1f9e0; 题目解析 题目描述&#xff1a; 给定两个字符串 s 和 p&#xff0c;找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义&#xff1a; 若两个字符串包含的字符种类和出现次数完全相同&#xff0c;顺序无所谓&#xff0c;则互为…...

用docker来安装部署freeswitch记录

今天刚才测试一个callcenter的项目&#xff0c;所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...

JVM 内存结构 详解

内存结构 运行时数据区&#xff1a; Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器&#xff1a; ​ 线程私有&#xff0c;程序控制流的指示器&#xff0c;分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 ​ 每个线程都有一个程序计数…...

人机融合智能 | “人智交互”跨学科新领域

本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...

如何更改默认 Crontab 编辑器 ?

在 Linux 领域中&#xff0c;crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用&#xff0c;用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益&#xff0c;允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...