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

CountDownLatch的原理

使用CountDownLatch可以实现等待多个线程执行完毕的功能,实现线程之间的协调,让它们按照我们期望的顺序执行,从而避免了可能出现的并发问题。

CountDownLatch是如何实现主线程等待子线程全部结束的呢?

在这里插入图片描述

代码用例
这里我们使用一段测试代码来理解它的原理,demo 如下:

public static void main(String[] args) throws InterruptedException {int nThreads = 5; // 需要等待的线程数CountDownLatch latch = new CountDownLatch(nThreads);Random random = new Random();for (int i = 0; i < nThreads; i++) {new Thread(() -> {// 执行任务System.out.println(Thread.currentThread().getName() + " is running...");try {Thread.sleep(random.nextInt(5000));} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + " finished...");// 任务完成,计数器减1latch.countDown();}).start();}// 等待所有线程执行完毕latch.await();System.out.println("All threads have finished.");
}

这个例子中,我们创建了一个CountDownLatch实例,并将需要等待的线程数设置为5。然后,我们启动5个线程来执行任务,每个线程执行的时间并不相同,并在每个线程执行完任务后调用 countDown() 方法将计数器减1。

最后,在主线程中我们调用 await() 方法来使当前线程等待,直到所有线程完成任务并计数器减为 0 为止。

原理
关键代码其实就三行。

new CountDownLatch(5) 创建 CountDownLatch 实例,设置为state 为 5,相当于是个信号量。
latch.countDown()任务执行完成,分别减少 state。
latch.await()主线程检查其他线程是否全部执行完成,否则等待。

信号量:那是多线程同步用的,一个线程完成了某一个动作就通过信号告诉别的线程,别的线程再进行某些动作。

互斥量:这是多线程互斥用的,比如说,一个线程占用了某一个资源,那么别的线程就无法访问,知道这个线程离开,其他的线程才开始可以利用这个资源。

创建对象
在创建 CountDownLatch 实例的时候,传入一个参数 5。它实现了内部类 Sync, 并且 Sync 继承了 AbstractQueuedSynchronizer(AQS)类。这里设置的 State 其实就是 AQS 中的 state 成员变量。

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

执行countDown
设置完线信号量之后,在每个线程执行结束后会执行 latch.countDown()将 state 减一。

private final Sync sync;
public void countDown() {sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {if (tryReleaseShared(arg)) {doReleaseShared();return true;}return false;
}

在 releaseShared 方法中,他会尝试 state 减1,这里是一个死循环,也就是自旋锁。通过CAS的方式线程安全的修改 state的值,如果不成功,就通过自旋锁不断去尝试。

private static final class Sync extends AbstractQueuedSynchronizer {protected int tryAcquireShared(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;//CASif (compareAndSetState(c, nextc))return nextc == 0;}}
}

在状态更新后会进入 doReleaseShared ,他会解锁这个队列中的所有线程。正常来说,走到这里都是运行状态的。但是如果主线程先走到了 await方法,这个队列中就会唤醒主线程检查state是否等于0。

子线程执行完后就结束了,不会添加到等待队列和同步队列中。

private void doReleaseShared() {for (;;) {Node h = head;if (h != null && h != tail) {int ws = h.waitStatus;//这里说明了头节点后面是有节点的,需要被唤醒。if (ws == Node.SIGNAL) {//CAS更新状态if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))continue;            // loop to recheck casesunparkSuccessor(h);}//head.waitStatus=0的情况有两种//1、就是head节点没有及时更新,线程被唤醒之后获取到了锁,在更新head之前,又经过一轮循环执行到这。  但是如果节点没有及时更新就会退出。所以执行到这一步只可能是情况2//2、head节点及时更新了,但是到了最后一个节点,它的head.waitStatus=0else if (ws == 0 &&!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))continue;                // loop on failed CAS}//节点没有及时更新,就退出。if (h == head)                   // loop if head changedbreak;}
}

最后一步:等待
在主线程执行的时候,到达 await方法会检查当前的 state 是否等于0,如果不等于就返回 -1,进入 doAcquireSharedInterruptibly 逻辑。

public void await() throws InterruptedException {sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();// 检查 state 是否等于0,不等于返回-1if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);
}

在 doAcquireSharedInterruptibly 方法中,会通过自旋锁不断检查 state。第二次自选检查的时候会被阻塞进入队列,然后等待子线程调用 countDown 方法的时候幻醒主线程,继续自选检查。

rivate void doAcquireSharedInterruptibly(int arg)throws InterruptedException {final Node node = addWaiter(Node.SHARED);boolean failed = true;try {for (;;) {final Node p = node.predecessor();if (p == head) {//自旋不断检查是否state=0int r = tryAcquireShared(arg);if (r >= 0) {setHeadAndPropagate(node, r);p.next = null; // help GCfailed = false;return;}}//第二次自旋,park等待if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}
}

相关文章:

CountDownLatch的原理

使用CountDownLatch可以实现等待多个线程执行完毕的功能&#xff0c;实现线程之间的协调&#xff0c;让它们按照我们期望的顺序执行&#xff0c;从而避免了可能出现的并发问题。 CountDownLatch是如何实现主线程等待子线程全部结束的呢&#xff1f; 代码用例 这里我们使用一段…...

Java新特性Stream流详解

一、概述 Stream流是Java 8 API添加的一个新的抽象&#xff0c;以一种声明性方式处理数据集合&#xff08;侧重对于源数据计算能力的封装&#xff0c;并且支持序列与并行两种操作方式&#xff09;。 Stream流是对集合&#xff08;Collection&#xff09;对象功能的增强&#xf…...

关于VScode中一些常用的快捷操作!

vscode CTRLO&#xff1a;打开文件夹以开始工作 先CTRLK 再CTRLO&#xff1a;打开文件夹以开始工作 如何选择workspace&#xff1a;file → open folder→选目标文件夹【当前工作区选择会影响代码是否能运行】 如何打开终端&#xff1a;View → terminal debug看不到变化历史&…...

Django 使用Mysql数据库

目录 Django 使用Mysql数据库本地安装Mysql数据服务安装好Pymysql服务Django配置数据库迁移各种报错无法找到mysqlclient数据库拒绝连接 Django 使用Mysql数据库 本地安装Mysql数据服务 安装好Pymysql服务 python3 -m pip install PyMySQL官方文档介绍 Django配置 官网文档 …...

js继承的几种方式(原型链继承、构造函数继承、组合式继承、寄生组合式继承、ES6的Class类继承)

1.原型链继承 实现原理&#xff1a;子类的原型指向父类实例。子类在自身实例上找不到属性和方法时去它父类实例&#xff08;父类实例和实例的原型对象&#xff09;上查找&#xff0c;从而实现对父类属性和方法的继承 缺点&#xff1a; 子类创建时不能传参&#xff08;即没有…...

AnyTransition/过渡动画, MatchedGeometryEffect/匹配几何动画效果 的使用

1. AnyTransition 过渡动画效果 1.1 创建过度动画案例 AnyTransitionBootcamp.swift import SwiftUI/// 旋转修饰 View struct RotateViewModifier :ViewModifier{let rotation: Doublefunc body(content: Content) -> some View {content.rotationEffect(Angle(degrees: r…...

mac版postman升级后数据恢复办法

postman升级了一下&#xff0c;所有的collections都丢失了。 首先在finder里找到这个路径 /Users/{用户名}/Library/Application Support/Postman找到升级之前的的最新的backup.json&#xff0c;然后在postman里import这个文件。 所有升级前的collections都恢复了&#xff0…...

四.镜头知识之放大倍率

四.镜头知识之放大倍率 文章目录 四.镜头知识之放大倍率4.0 前言4.1 镜头的光学放大倍率的计算方法4.2 显示器的电子放大倍率4.2.1 智能硬件产品的显示放大倍率计算案例4.3 系统放大倍率4.4 智能硬件产品的系统放大倍率计算案例4.4 智能硬件产品的系统放大倍率计算案例4.0 前言…...

Jenkins UI 自动化持续化集成测试

一&#xff1a;安装jenkins 环境 在官网下载msi 直接安装即可 二&#xff1a;设置全局变量 设置allure 路径 三&#xff1a;创建项目 1、创建自由风格项目 2、如果项目在本地&#xff0c;且本地服务器是windows &#xff0c;找到Jenkins安装根目录&#xff0c;寻找config…...

vue项目中引入地图的详细教程

第一步&#xff1a;在项目中安装地图插件 npm i amap/amap-jsapi-loader --save 第二步&#xff1a;创建一个容器 添加id属性 &#xff08;因为地图必须使用id 不能使用class&#xff09; <div id"maps"></div> 第三步&#xff1a;给这个容器设置宽…...

MyBatisPlus 多数据源配置

目录 一、mybatis-plus 简介 特性 二、支持数据库&#xff1a; 三、 开发实例 1. 引入依赖&#xff1a; 2. 参数配置application.yml 3. 在 Spring Boot 启动类中添加 MapperScan 注解&#xff0c;扫描 Mapper 文件夹&#xff1a; 4. 编写实体类 User.java&#xff08;此处…...

使用Golang实现HTTP代理突破IP访问限制

引言 在当今互联网时代&#xff0c;网站和服务商为了维护安全性和保护用户隐私&#xff0c;常常会对特定的IP地址进行封锁或限制。但是&#xff0c;有时候我们可能需要访问这些被限制的网站或服务。为了突破这种限制&#xff0c;我们可以使用HTTP代理来隐藏真实的客户端IP地址…...

Iterator和ListIterator的区别是什么?

Iterator 和 ListIterator 都是 Java 集合框架中的迭代器,其中 Iterator 是普遍适用于所有实现了 Iterable 接口的集合类的通用迭代器,而 ListIterator 则是专门用于遍历 List 集合的迭代器,它比 Iterator 更加强大,而且只适用于 List 集合。 以下是 Iterator 和 ListItera…...

大坑-MATLAB图片转存时需注意的点

MATLAB中图片的保存和转存有一个巨大的陷阱&#xff0c;我也是在吃了大亏后发现的&#xff0c;正常情况下&#xff0c;MATLAB跑完实验&#xff0c;生成的图片如下 放大后这样 可以方便修改坐标轴标题&#xff0c;最初我就是因为想修改坐标轴标题才给它放大的&#xff0c;因为…...

基于Lang-Chain(ChatGLM和ChatChat)知识库大语言模型的部署搭建

环境准备 阿里云个人认证后&#xff0c;可免费试用机器学习平台PAI&#xff0c;可提供适合大语言模型环境搭建的高配置服务器。 点击试用阿里云服务器 试用产品选择&#xff1a;选择交互式建模PAI-DSW 适合哪些场景 文章/知识库/帮助文档等的检索基于现有知识库实现问答… …...

个人轻博客PHP开源系统/溯雪Sxlog轻博客源码/洁干净轻/占内存极低/php源码

源码简介&#xff1a; 溯雪(sxlog)它是一款很简洁越低内存的轻博客程序&#xff0c;整个程序包不到200KB&#xff0c;占内存极小&#xff0c;比一张照片都要小很多。简洁高效&#xff0c;占用空间内存极小&#xff0c;而且它不依赖任何数据库&#xff0c;不依赖富文本编辑器&a…...

2.Vue-从零开始搭建一个vue项目

题记 从零开始搭建一个vue项目&#xff0c;以下是操作的全过程。 安装Vue CLI脚手架 打开终端&#xff0c;运行以下命令全局安装Vue CLI脚手架&#xff1a; npm install -g vue/cli 查看 Vue CLI脚手架版本&#xff1a; vue -V 注意&#xff1a;查看vue版本的命令不是vue -V&a…...

快速构建代理应对

今天我要和大家分享一个解决反爬策略升级问题的方法&#xff0c;那就是快速构建代理池。如果您是一位爬虫开发人员&#xff0c;一定深知反爬策略的烦恼。但是&#xff0c;通过构建代理池&#xff0c;您可以轻松地应对反爬策略的升级&#xff0c;让您的爬虫持续高效运行。接下来…...

【LeetCode刷题(数据结构)】:另一颗树的子树

给你两棵二叉树 root 和 subRoot 检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子…...

LeetCode 2903. 找出满足差值条件的下标 I【双指针+维护最大最小】简单

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…...

Volcano 进阶实战:网络拓扑与负载感知调度的深度协同

1. 从单打独斗到并肩作战&#xff1a;为什么我们需要协同调度&#xff1f; 大家好&#xff0c;我是老张&#xff0c;在AI基础设施这块摸爬滚打了十来年&#xff0c;亲眼看着集群规模从几十台服务器膨胀到成千上万台。早期做模型训练&#xff0c;调度器只管一件事&#xff1a;把…...

纯净版驱动管理工具,一键安装备份还原

软件介绍 今天说的这款工具叫驱动大师&#xff0c;来自360家。很多人一提到360的软件就皱眉头&#xff0c;甚至说我推荐它肯定是收了钱。说实话挺无奈的&#xff0c;一分钱没收&#xff0c;纯粹是因为它好用才分享&#xff0c;尤其是这个纯净版本。 一键安装驱动 打开软件后&…...

开箱即用:万物识别镜像Gradio界面快速体验教程

开箱即用&#xff1a;万物识别镜像Gradio界面快速体验教程 1. 引言&#xff1a;让AI视觉识别像打开网页一样简单 想象一下&#xff0c;你拿到了一张照片&#xff0c;里面有个不认识的植物、一个没见过的零件&#xff0c;或者一件叫不上名字的老物件。以前你可能需要上网搜索、…...

仅限核心开发者查阅:MCP本地DB连接器v2.4.0源码加密配置模块逆向还原(含AES-256密钥派生流程图)

第一章&#xff1a;MCP本地DB连接器v2.4.0源码加密配置模块逆向还原总览MCP本地DB连接器v2.4.0的加密配置模块采用混合式保护策略&#xff0c;结合编译期混淆、运行时密钥派生与AES-256-GCM动态解密三重机制。该模块不依赖外部密钥管理服务&#xff08;KMS&#xff09;&#xf…...

Qwen3-14B部署教程:从Docker镜像拉取到Chainlit网页访问完整流程

Qwen3-14B部署教程&#xff1a;从Docker镜像拉取到Chainlit网页访问完整流程 1. 环境准备与快速部署 在开始之前&#xff0c;请确保您的系统满足以下基本要求&#xff1a; 操作系统&#xff1a;Linux&#xff08;推荐Ubuntu 20.04&#xff09;显卡&#xff1a;NVIDIA GPU&am…...

Qwen3-14B镜像免配置部署:一键运行vLLM服务+Chainlit前端调用

Qwen3-14B镜像免配置部署&#xff1a;一键运行vLLM服务Chainlit前端调用 1. 模型简介 Qwen3-14b_int4_awq是基于Qwen3-14b模型的量化版本&#xff0c;采用了int4精度和AWQ&#xff08;Activation-aware Weight Quantization&#xff09;量化技术。这个版本通过AngelSlim工具进…...

基于GD32F470的便携式NES模拟器嵌入式系统设计

1. 项目概述基于GD32F470ZGT6高性能微控制器构建的便携式NES游戏机&#xff0c;是一个面向嵌入式系统学习与实践的综合性硬件平台。该项目并非简单复刻经典游戏机的外观形态&#xff0c;而是以NES&#xff08;Nintendo Entertainment System&#xff09;模拟器为核心功能载体&a…...

AI 不会先杀死 SaaS,但会先杀死 SaaS 的旧玩法

今天和大家聊一下 AI 对 SaaS 生意的冲击。导火索是 2026 年2月初的一次市场震动&#xff1a;Anthropic 发布了新一代法律与知识工作自动化工具后&#xff0c;投资人突然开始担心&#xff1a;大模型不再只是底层能力&#xff0c;而是在直接往软件公司的应用层上走了。紧接着&am…...

让 AI 成为你的鸿蒙开发助手:harmonyos-build-deploy Skill 完全实战指南

&#x1f680; 让 AI 成为你的鸿蒙开发助手&#xff1a;harmonyos-build-deploy Skill 完全实战指南 不是一篇教你敲命令的文章&#xff0c;而是一场关于 AI Coding 范式的革命。 在 HarmonyOS 开发中&#xff0c;我们不再需要记忆繁琐的 CLI 参数&#xff0c;不再需要手动处理…...

Java 后端实现 token自动续期,这方案有点优雅!

在前后端分离的开发模式下&#xff0c;前端用户登录成功后后端服务会给用户颁发一个token。前端(如vue)在接收到 token后会将token存储到LocalStorage中。后续每次请求都会将此token放在请求头中传递到后端服务&#xff0c;后端服务会有一个过滤器对token进行拦截校验&#xff…...