CountDownLatch实现原理全面解析

简介

CountDownLatch是一个同步工具类,用来协调多个线程之间的同步(即:用于线程之间的通信而不是互斥)。它允许一个或多个线程进入等待状态,直到其他线程执行完毕后,这些等待的线程才继续执行。

CountDownLatch通过一个计数器来实现,其中维护了一个count变量和操作该变量的两个主要方法:

  • await()方法:线程调用await()方法,会使调用该方法的线程进入阻塞状态,并将其加入到阻塞队列中。
  • countDown()方法:线程调用countDown()方法,会将CountDownLatch中count的值-1。当count变量的值递减为0,会唤醒阻塞队列中调用await()方法的线程继续执行业务处理。

应用场景

CountDownLatch是一种非常实用的并发控制工具,它的主要应用场景:

  • 主线程等待多个子线程完成任务处理。如:主线程等待其他线程各自完成任务处理后,再继续执行。

  • 实现多个线程开始执行任务处理的最大并行性(注意:是并行而非并发)。如:多个线程需要在同一时刻开始执行任务处理,可以通过如下方式实现:

    1)初始化一个的CountDownLatch变量(计数器的初始化值为1)。

    2)需要在同一时刻执行任务处理的所有线程调用CountDownLatch.await()方法进入阻塞状态。

    3)主线程调用CountDownLatch.countDown()方法将计数器-1(此时计数器的值为0),唤醒所有调用CountDownLatch.await()方法进入阻塞状态的线程开始执行任务处理。

实现原理

CountDownLatch中定义了一个Sync类型的变量和操作该变量的方法。

源码如下:

// Sync类型的同步变量
private final Sync sync;
// 构造函数,用于初始化CountDownLatch计数器
public CountDownLatch(int count) {...}
// 当前线程进入阻塞状态,直到AQS中的state(计数器)值为0,或者当前线程被其他线程中断。
public void await() throws InterruptedException {...}
// 当前线程进入阻塞状态,直到AQS中的state(计数器)值为0,或者当前线程等待超时或者被其他线程中断。
public boolean await(long timeout, TimeUnit unit) throws InterruptedException {...}
// 递减AQS中的state(计数器)值,如果state的值递减为0,则唤醒调用await()方法进入阻塞的线程。
public void countDown() {...}
// 返回state的值。
public long getCount() {...}
// 返回标识CountDownLatch及其计数器值的字符串。
public String toString() {...}

其中,最重要的是sync类型的变量、await()和countDown()方法。

Sync

Sync是CountDownLatch的静态内部类器,它继承了
AbstractQueuedSynchronizer(AQS),主要用于CountDownLatch的同步状态,创建CountDownLatch时进行初始化。

CountDownLatch构造函数:

public CountDownLatch(int count) {// 如果传入的count值小于0,则抛出IllegalArgumentException异常if (count < 0) throw new IllegalArgumentException("count < 0");// 初始化Syncthis.sync = new Sync(count);
}

初始化Sync时,将传入的count参数值赋值给AQS的同步状态state,state是一个volatile修饰的int值,一个线程修改了state值,其他线程能够立刻感知,从而保证state值在并发场景下的可见性。

同时,Sync实现了AQS的tryAcquireShared()和tryReleaseShared()方法:

java.util.concurrent.CountDownLatch.Sync#tryAcquireShared
// 尝试获取共享资源
protected int tryAcquireShared(int acquires) {/*** 用于根据state(计数器)的值来尝试获取共享资源:*   state的值为0,返回1,表示可以获取共享资源。*   state的值不为0,返回-1,表示无法获取共享资源。*/return (getState() == 0) ? 1 : -1;
}
// 尝试释放共享资源(AQS)
protected boolean tryReleaseShared(int releases) {// Decrement count; signal when transition to zerofor (;;) {// 获取当前state的值int c = getState();// 如果state的值为0,则返回false(即:没有需要释放的资源)if (c == 0)return false;// 如果state的值大于0,则将state的值-1,并通过CAS的方式更新state的最新值int nextc = c-1;if (compareAndSetState(c, nextc))/** * 返回资源释放结果:*   释放资源后state的值为0,则返回true,表示可以唤醒调用await()方法进入阻塞的线程。*   释放资源后state的值不为0,则返回false,表示继续阻塞调用await()方法的线程,直到state的值被减为0。*/return nextc == 0;}
}

await方法

CountDownLatch通过CountDownLatch#await方法调用CountDownLatch.Sync#tryAcquireShared方法尝试获取共享资源:

  • 获取到共享资源,则唤醒调用await()方法的线程执行业务处理。
  • 获取不到共享资源,则继续阻塞调用await()方法的线程,直到state的值递减为0(即:其他线程释放完共享资源)。

CountDownLatch#await方法源码解析:

// java.util.concurrent.CountDownLatch#await()
public void await() throws InterruptedException {// 获取共享资源(可中断)sync.acquireSharedInterruptibly(1);
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireSharedInterruptibly
// 获取共享资源(AQS)
public final void acquireSharedInterruptibly(int arg)throws InterruptedException {// 如果线程被其他线程中断,则抛出InterruptedException异常if (Thread.interrupted())throw new InterruptedException();// 具体由继承AQS的Sync的tryAcquireShared()方法实现if (tryAcquireShared(arg) < 0)// 如果获取共享资源锁失败,则将当前线程封装成Node节点追加到CLH队列的末尾,等待被唤醒(即:进入阻塞)doAcquireSharedInterruptibly(arg);
}

其中,
doAcquireSharedInterruptibly()方法源码解析请移步主页查阅->「一文搞懂」AQS(抽象队列同步器)实现原理及源码解析。

countDown方法

CountDownLatch通过CountDownLatch#countDown方法调用CountDownLatch.Sync#tryReleaseShared方法尝试释放共享资源:

  • 如果释放某个共享资源后state的值为0,则唤醒调用await()方法的线程执行业务处理。
  • 如果释放某个共享资源后state的值不为0,则继续阻塞调用await()方法的线程,直到state的值被减为0。

CountDownLatch#countDown方法源码解析:

// java.util.concurrent.CountDownLatch#countDown
public void countDown() {// 释放共享资源(数量为1)sync.releaseShared(1);
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer#releaseShared
// 释放共享资源(AQS)
public final boolean releaseShared(int arg) {// 具体由继承AQS的Sync的tryReleaseShared()方法实现if (tryReleaseShared(arg)) {// 唤醒后继节点doReleaseShared();return true;}return false;
}

使用示例

主线程等待子线程完成处理

/*** @author 南秋同学* 主线程等待多个子线程完成任务处理*/
@Slf4j
public class CountDownLatchExample {@SneakyThrowspublic static void main(String[] args) {// 初始化一个的CountDownLatch变量(计数器的初始化值为5)CountDownLatch cdl = new CountDownLatch(5);// 初始化一个固定大小的线程池ExecutorService service = Executors.newFixedThreadPool(5);for(int i = 0; i < 5 ; i++){// 创建Runnable线程Runnable runnable = new Runnable() {@SneakyThrows@Overridepublic void run() {log.info("子线程-{}开始执行...", Thread.currentThread().getName());Thread.sleep((long) (Math.random() * 10000));log.info("子线程-{}执行完成", Thread.currentThread().getName());cdl.countDown();}};service.execute(runnable);}log.info("主线程-{}等待所有子线程执行完成...",Thread.currentThread().getName());cdl.await();log.info("所有子线程执行完成,开始执行主线程-{}",Thread.currentThread().getName());}
}

执行结果:

14:13:01.299 [pool-1-thread-3]  - 子线程-pool-1-thread-3开始执行...
14:13:01.299 [pool-1-thread-2]  - 子线程-pool-1-thread-2开始执行...
14:13:01.299 [main]  - 主线程-main等待所有子线程执行完成...
14:13:01.299 [pool-1-thread-1]  - 子线程-pool-1-thread-1开始执行...
14:13:01.299 [pool-1-thread-5]  - 子线程-pool-1-thread-5开始执行...
14:13:01.299 [pool-1-thread-4]  - 子线程-pool-1-thread-4开始执行...
14:13:02.739 [pool-1-thread-3]  - 子线程-pool-1-thread-3执行完成
14:13:03.792 [pool-1-thread-1]  - 子线程-pool-1-thread-1执行完成
14:13:04.752 [pool-1-thread-5]  - 子线程-pool-1-thread-5执行完成
14:13:07.761 [pool-1-thread-4]  - 子线程-pool-1-thread-4执行完成
14:13:10.384 [pool-1-thread-2]  - 子线程-pool-1-thread-2执行完成
14:13:10.385 [main]  - 所有子线程执行完成,开始执行主线程-main

多线程最大并行处理

/*** @author 南秋同学* 实现多个线程开始执行任务处理的最大并行性*/
@Slf4j
public class CountDownLatchExample {@SneakyThrowspublic static void main(String[] args) {// 初始化一个的CountDownLatch变量(计数器的初始化值为1)CountDownLatch referee = new CountDownLatch(1);// 初始化一个的CountDownLatch变量(计数器的初始化值为5)CountDownLatch sportsman = new CountDownLatch(5);// 初始化一个固定大小的线程池ExecutorService service = Executors.newFixedThreadPool(5);for(int i = 0; i < 5 ; i++){// 创建Runnable线程Runnable runnable = new Runnable() {@SneakyThrows@Overridepublic void run() {log.info("运动员-{},等待裁判发布开始口令", Thread.currentThread().getName());referee.await();log.info("运动员-{},收到裁判发布的开始口令,起跑...", Thread.currentThread().getName());Thread.sleep((long) (Math.random() * 10000));log.info("运动员-{}到达终点", Thread.currentThread().getName());sportsman.countDown();}};service.execute(runnable);}log.info("裁判-{}准备发布开始口令...",Thread.currentThread().getName());Thread.sleep((long) (Math.random() * 10000));referee.countDown();log.info("裁判-{}已经发布开始口令,等待所有选手达到终点...",Thread.currentThread().getName());sportsman.await();log.info("所有运动员达到终点,裁判-{}开始计分",Thread.currentThread().getName());}
}

执行结果:

13:56:14.683 [pool-1-thread-3]  - 运动员-pool-1-thread-3,等待裁判发布开始口令
13:56:14.683 [pool-1-thread-2]  - 运动员-pool-1-thread-2,等待裁判发布开始口令
13:56:14.683 [pool-1-thread-5]  - 运动员-pool-1-thread-5,等待裁判发布开始口令
13:56:14.683 [main]  - 裁判-main准备发布开始口令...
13:56:14.683 [pool-1-thread-1]  - 运动员-pool-1-thread-1,等待裁判发布开始口令
13:56:14.683 [pool-1-thread-4]  - 运动员-pool-1-thread-4,等待裁判发布开始口令
13:56:18.205 [main]  - 裁判-main已经发布开始口令,等待所有选手达到终点...
13:56:18.205 [pool-1-thread-2]  - 运动员-pool-1-thread-2,收到裁判发布的开始口令,起跑...
13:56:18.205 [pool-1-thread-3]  - 运动员-pool-1-thread-3,收到裁判发布的开始口令,起跑...
13:56:18.206 [pool-1-thread-5]  - 运动员-pool-1-thread-5,收到裁判发布的开始口令,起跑...
13:56:18.206 [pool-1-thread-4]  - 运动员-pool-1-thread-4,收到裁判发布的开始口令,起跑...
13:56:18.206 [pool-1-thread-1]  - 运动员-pool-1-thread-1,收到裁判发布的开始口令,起跑...
13:56:22.110 [pool-1-thread-4]  - 运动员-pool-1-thread-4到达终点
13:56:23.866 [pool-1-thread-1]  - 运动员-pool-1-thread-1到达终点
13:56:26.803 [pool-1-thread-3]  - 运动员-pool-1-thread-3到达终点
13:56:28.019 [pool-1-thread-5]  - 运动员-pool-1-thread-5到达终点
13:56:28.178 [pool-1-thread-2]  - 运动员-pool-1-thread-2到达终点
13:56:28.179 [main]  - 所有运动员达到终点,裁判-main开始计分

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/727502.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

干货分享③:免费制作产品管理系统!

他来了&#xff0c;他来了&#xff0c;他带着码上飞CodeFlying走来了&#xff01;今天继续为大家带来一期干货分享&#xff0c;教大家如何免费使用码上飞来的开发产品管理系统 &#xff01; 一、登陆官网 码上飞 CodeFlying | AI 智能软件开发平台&#xff01; 点击立即体验注…

Learn OpenGL 01

OpenGL的定义 一般它被认为是一个API(Application Programming Interface, 应用程序编程接口)&#xff0c;包含了一系列可以操作图形、图像的函数。然而&#xff0c;OpenGL本身并不是一个API&#xff0c;它仅仅是一个由Khronos组织制定并维护的规范(Specification)。 OpenGL规…

服务器严重不够啊

必需采购服务器了&#xff0c;

一个比较全面实用的C#帮助类、工具类库

前言 经常会有一些同学会问为什么感觉我身边的大佬写一个功能会这么快&#xff1f;一个类似的模块大佬可能半天就搞定了&#xff0c;而我要搞一两天。其实工作久了你会发现很多常用公共的帮助类和工具类&#xff0c;如常见的Excel数据导入导出、文件操作、字符串操作、数据转换…

SpringBoot源码解读与原理分析(一)SpringBoot整体概述

文章目录 第1章 SpringBoot整体概述1.1 Spring Framework1.1.1 Spring Framework的历史1.1.2 IOC与AOP 1.2 Spring Boot与Spring Framework1.3 Spring Boot的核心特性1.4 Spring Boot的体系 第1章 SpringBoot整体概述 Spring Framework 开发团队 支持不依赖外部容器的Web应用程…

从零搭建React18.2+ReactRoute6.22+TS5+RTK2.2搭配antd5+antd-style书写All in Js完整体验项目规范

1. 使用CRA创建项目 全局设置npm淘宝镜像源 npm config set registry https://registry.npmmirror.com -g使用最新版create-react-app初始化项目结构 npx create-react-app custom-template --template typescript初始化项目之后在package.json文件中配置使用node>18.0.0…

WordPress供求插件API文档:用户登录

该文档为WordPress供求插件文档&#xff0c;详情请查看 WordPress供求插件&#xff1a;一款专注于同城生活信息发布的插件-CSDN博客文章浏览阅读67次。WordPress供求插件&#xff1a;sliver-urban-life 是一款专注于提供同城生活信息发布与查看的插件&#xff0c;该插件可以实…

首次在Java8中,使用jni调用C的dll

这次是使用C语言生成的dll 以下是在Java 8中使用JNI调用DLL的步骤清单&#xff1a; 编写Java类接口&#xff1a;创建一个Java接口&#xff0c;定义与本地方法对应的方法签名。 public interface MyNativeInterface {void nativeMethod(); }编写Java类实现&#xff1a;创建一…

windows下编译boost1.84.0库

boost系列文章目录 文章目录 boost系列文章目录前言一、boost编译二、boost使用三 、参考 前言 Boost简介 官方网址 Boost提供免费的同行评审的可移植C源代码库。 我们强调与C标准库配合良好的库。Boost库旨在广泛使用&#xff0c;并可在广泛的应用程序中使用。Boost许可证鼓…

一键打通红圈泛微,让协同办公更轻松!

客户介绍 某投资集团有限公司是一家总部位于中国深圳的多元化投资控股集团。自成立以来&#xff0c;该公司始终秉持着稳健经营、持续创新的理念&#xff0c;深耕多个领域&#xff0c;包括但不限于金融、地产、科技及文化产业等。展望未来&#xff0c;该公司将继续坚持创新驱动…

SpringBoot+Maven多环境配置模式

我这里有两个配置文件 然后在最外层的父级POM文件里面把这个两个配置文件写上 <profiles><profile><id>druid</id><properties><spring.profiles.active>druid</spring.profiles.active></properties><activation><…

算法训练day38动态规划基础Leetcode509斐波纳切数70爬楼梯746使用最小花费爬楼梯

什么是动态规划 对于动态规划问题&#xff0c;我将拆解为如下五步曲&#xff0c;这五步都搞清楚了&#xff0c;才能说把动态规划真的掌握了&#xff01; 确定dp数组&#xff08;dp table&#xff09;以及下标的含义确定递推公式dp数组如何初始化确定遍历顺序举例推导dp数组&a…

关于买手机和消费观念的小讨论

大家好&#xff0c;我是阿赵。   现在手机已经基本上是人手一台了&#xff0c;出门可以不带钱包&#xff0c;不能不带手机。阿赵我作为一个IT工作者&#xff0c;手机游戏开发者&#xff0c;这里给大家分享一下我这20多年来买手机的一些经历。   在阿赵我读大学的时候&#…

sensor_msgs::LaserScan雷达数据转换成pcl::PointXYZ数据类型

文章目录 创建工作空间、功能包功能包文件 package.xml头文件、源码文件laserscan_to_pointcloud.hlaserscan_to_pointcloud.cc 代码解读编译文件 CMakeLists.txt运行效果 截图 创建工作空间、功能包 catkin_create_pkg laserscan_to_pointcloud sensor_msgs roscpp pcl_conve…

分享关于如何解决系统设计问题的逐步框架

公司广泛采用系统设计面试&#xff0c;因为在这些面试中测试的沟通和解决问题的技能与软件工程师日常工作所需的技能相似。面试官的评估基于她如何分析一个模糊的问题以及如何逐步解决问题。测试的能力还包括她如何解释这个想法&#xff0c;与他人讨论&#xff0c;以及评估和优…

学习JAVA的第十六天(基础)

目录 双列集合 Map双列集合 遍历方式 键找值 键值对 Lambda表达式 HashMap集合 LinkedHashMap集合 TreeMap集合 可变参数 前言&#xff1a;学习JAVA的第十五天&#xff08;基础&#xff09;-CSDN博客 双列集合 特点&#xff1a; 双列集合一次需要存放一对数据&#x…

回溯算法07-子集(Java/子集问题)

.子集 题目描述 给你一个整数数组 nums &#xff0c;数组中的元素 互不相同 。返回该数组所有可能的子集&#xff08;幂集&#xff09;。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[],[…

ChatGPT高效提问——说明提示技巧

ChatGPT高效提问——说明提示技巧 现在&#xff0c;让我们开始体验“说明提示技巧”&#xff08;IPT, Instructions Prompt Technique&#xff09;和如何用它生成来自ChatGPT的高质量的文本。说明提示技巧是一个通过向ChatGPT提供需要依据的具体的模型的说明来指导ChatGPT输出…

不伤耳朵的蓝牙耳机推荐,骨传导耳机选购前必知的几大要点

在目前的蓝牙耳机市场上&#xff0c;骨传导蓝牙耳机以其独特的设计和不伤耳朵的好处而受到广泛关注。骨传导蓝牙耳机通过骨头传导声音&#xff0c;无需进入耳道&#xff0c;从而减少了耳朵的不适和潜在伤害。 骨传导蓝牙耳机这种开放式的设计允许用户在享受音乐的同时&#xf…

lvs+keepalive

虚拟路由冗余协议(Virtual Router Redundancy Protocol&#xff0c;简称VRRP) VRRP能够在不改变组网的情况下&#xff0c;将多台路由器虚拟成一个虚拟路由器&#xff0c;通过配置虚拟路由器的IP地址为默认网关&#xff0c;实现网关的备份。 协议版本: VRRPv2&#xff08;常用&…