一文彻底弄懂JUC工具包的CountDownLatch的设计理念与底层原理
CountDownLatch 是 Java 并发包(java.util.concurrent)中的一个同步辅助类,它允许一个或多个线程等待一组操作完成。
一、设计理念
CountDownLatch 是基于 AQS(AbstractQueuedSynchronizer)实现的。其核心思想是维护一个倒计数,每次倒计数减少到零时,等待的线程才会继续执行。它的主要设计目标是允许多个线程协调完成一组任务。
1. 构造函数与计数器
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
构造 CountDownLatch 时传入的 count 决定了计数器的初始值。该计数器控制了线程的释放。
2. AQS 支持的核心操作
AQS 是 CountDownLatch 的基础,通过自定义内部类 Sync 实现,Sync 继承了 AQS 并提供了必要的方法。以下是关键操作:
acquireShared(int arg): 如果计数器值为零,表示所有任务已完成,线程将获得许可。releaseShared(int arg): 每次调用countDown(),会减少计数器,当计数器降到零时,AQS 将释放所有等待的线程。
3. 实现细节
countDown():调用releaseShared()减少计数器,并通知等待线程。await():调用acquireSharedInterruptibly(1),如果计数器非零则阻塞等待。
二、底层原理
CountDownLatch 的核心是基于 AbstractQueuedSynchronizer(AQS)来管理计数器状态的。AQS 是 JUC 中许多同步工具的基础,通过一个独占/共享模式的同步队列实现线程的管理和调度。CountDownLatch 采用 AQS 的共享锁机制来控制多个线程等待一个条件。
1. AQS 的共享模式
AQS 设计了两种同步模式:独占模式(exclusive)和共享模式(shared)。CountDownLatch 使用共享模式:
- 独占模式:每次只能一个线程持有锁,如
ReentrantLock。 - 共享模式:允许多个线程共享锁状态,如
Semaphore和CountDownLatch。
CountDownLatch 的 await() 和 countDown() 方法对应于 AQS 的 acquireShared() 和 releaseShared() 操作。acquireShared() 会检查同步状态(计数器值),若状态为零则立即返回,否则阻塞当前线程,进入等待队列。releaseShared() 用于减少计数器并唤醒所有等待线程。
2. Sync 内部类的设计
CountDownLatch 通过一个私有的内部类 Sync 来实现同步逻辑。Sync 继承自 AQS,并重写 tryAcquireShared(int arg) 和 tryReleaseShared(int arg) 方法。
static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count);
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// 自旋减计数器
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c - 1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
- tryAcquireShared(int):当计数器为零时返回 1(成功获取锁),否则返回 -1(阻塞)。
- tryReleaseShared(int):每次
countDown()减少计数器值,当计数器到达零时返回true,唤醒所有阻塞线程。
3. CAS 操作确保线程安全
tryReleaseShared 方法使用 CAS(compare-and-set)更新计数器,避免了锁的开销。CAS 操作由 CPU 原语(如 cmpxchg 指令)支持,实现了高效的非阻塞操作。这种设计保证了 countDown() 的线程安全性,使得多个线程能够并发地减少计数器。
4. 内部的 ConditionObject
CountDownLatch 不支持复用,因为 AQS 的 ConditionObject 被设计为单一触发模式。计数器一旦降至零,CountDownLatch 无法重置,只能释放所有线程,而不能再次设置初始计数器值。这就是其不可复用的根本原因。
三、应用场景
- 等待多线程任务完成:
CountDownLatch常用于需要等待一组线程完成其任务后再继续的场景,如批处理任务。 - 并行执行再汇总:在某些数据分析或计算密集型任务中,将任务分割成多个子任务并行执行,主线程等待所有子任务完成后再汇总结果。
- 多服务依赖协调:当一个服务依赖多个其他服务时,可以使用
CountDownLatch来同步各个服务的调用,并确保所有依赖服务准备好之后再执行主任务。
四、示例代码
以下示例展示如何使用 CountDownLatch 实现一个并发任务等待所有子任务完成的机制。
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
private static final int TASK_COUNT = 5;
private static CountDownLatch latch = new CountDownLatch(TASK_COUNT);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < TASK_COUNT; i++) {
new Thread(new Task(i + 1, latch)).start();
}
// 主线程等待所有任务完成
latch.await();
System.out.println("所有任务已完成,继续主线程任务");
}
static class Task implements Runnable {
private final int taskNumber;
private final CountDownLatch latch;
Task(int taskNumber, CountDownLatch latch) {
this.taskNumber = taskNumber;
this.latch = latch;
}
@Override
public void run() {
try {
System.out.println("子任务 " + taskNumber + " 开始执行");
Thread.sleep((int) (Math.random() * 1000)); // 模拟任务执行时间
System.out.println("子任务 " + taskNumber + " 完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown(); // 完成一个任务,计数器减一
}
}
}
}
五、与其他同步工具的对比
1. CyclicBarrier
原理和用途:
CyclicBarrier也允许一组线程相互等待,直到所有线程到达屏障位置(barrier point)。- 它适合用于多阶段任务或分阶段汇聚,如处理分块计算时每阶段汇总结果。
底层实现:
CyclicBarrier内部通过ReentrantLock和Condition实现,屏障次数可以重置,从而支持循环使用。
与 CountDownLatch 的对比:
CyclicBarrier的可复用性使其适合重复的同步场景,而CountDownLatch是一次性的。CountDownLatch更灵活,允许任意线程调用countDown(),适合分布式任务。CyclicBarrier需要指定的线程达到屏障。
2. Semaphore
原理和用途:
Semaphore主要用于控制资源访问的并发数量,如限制数据库连接池的访问。
底层实现:
Semaphore基于 AQS 的共享模式实现,类似于CountDownLatch,但允许通过指定的“许可证”数量控制资源。
与 CountDownLatch 的对比:
Semaphore可以动态增加/减少许可,而CountDownLatch只能递减。Semaphore适合控制访问限制,而CountDownLatch用于同步点倒计数。
3. Phaser
原理和用途:
Phaser是CyclicBarrier的增强版,允许动态调整参与线程的数量。- 适合多阶段任务同步,并能随时增加或减少参与线程。
底层实现:
Phaser内部包含一个计数器,用于管理当前阶段的参与线程,允许任务动态注册或注销。
与 CountDownLatch 的对比:
Phaser更适合复杂场景,能够灵活控制阶段和参与线程;CountDownLatch的结构简单,只能用于一次性同步。Phaser的设计更复杂,适合长时间、多线程协调任务,而CountDownLatch更适合简单任务等待。
4、总结
CountDownLatch 是一个轻量级、不可复用的倒计数同步器,适合简单的一次性线程协调。其基于 AQS 的共享锁实现使得线程等待和计数器更新具有高效的并发性。虽然 CountDownLatch 不具备重用性,但其设计简洁,尤其适合需要等待多线程任务完成的场景。
与其他 JUC 工具相比:
CyclicBarrier更适合多阶段同步、阶段性汇总任务。Semaphore适合资源访问控制,具有可控的许可量。Phaser灵活性更高,适合动态参与线程、复杂多阶段任务。
选择适合的同步工具,取决于任务的性质、线程参与动态性以及是否需要重用同步控制。
一文彻底弄懂JUC工具包的CountDownLatch的设计理念与底层原理的更多相关文章
- 一文彻底弄懂cookie、session、token
前言 作为一个JAVA开发,之前有好几次出去面试,面试官都问我,JAVAWeb掌握的怎么样,我当时就不知道怎么回答,Web,日常开发中用的是什么?今天我们来说说JAVAWeb最应该掌握的三个内容. 发 ...
- 一文彻底弄懂this关键字用法
哈喽,大家好,我是指北君. 介绍完 native.static.final 关键字后,指北君再接再厉,接着为大家介绍另一个常用的关键字--this. this 也是Java中的一个关键字,在<J ...
- 一文弄懂神经网络中的反向传播法——BackPropagation【转】
本文转载自:https://www.cnblogs.com/charlotte77/p/5629865.html 一文弄懂神经网络中的反向传播法——BackPropagation 最近在看深度学习 ...
- 一文弄懂-Netty核心功能及线程模型
目录 一. Netty是什么? 二. Netty 的使用场景 三. Netty通讯示例 1. Netty的maven依赖 2. 服务端代码 3. 客户端代码 四. Netty线程模型 五. Netty ...
- 一文弄懂-《Scalable IO In Java》
目录 一. <Scalable IO In Java> 是什么? 二. IO架构的演变历程 1. Classic Service Designs 经典服务模型 2. Event-drive ...
- 一文弄懂-BIO,NIO,AIO
目录 一文弄懂-BIO,NIO,AIO 1. BIO: 同步阻塞IO模型 2. NIO: 同步非阻塞IO模型(多路复用) 3.Epoll函数详解 4.Redis线程模型 5. AIO: 异步非阻塞IO ...
- 一文弄懂CGAffineTransform和CTM
一文弄懂CGAffineTransform和CTM 一些概念 坐标空间(系):视图(View)坐标空间与绘制(draw)坐标空间 CTM:全称current transformation matrix ...
- 【TensorFlow】一文弄懂CNN中的padding参数
在深度学习的图像识别领域中,我们经常使用卷积神经网络CNN来对图像进行特征提取,当我们使用TensorFlow搭建自己的CNN时,一般会使用TensorFlow中的卷积函数和池化函数来对图像进行卷积和 ...
- 一文带你弄懂 JVM 三色标记算法!
大家好,我是树哥. 最近和一个朋友聊天,他问了我 JVM 的三色标记算法.我脑袋一愣发现竟然完全不知道!于是我带着疑问去网上看了几天的资料,终于搞清楚啥事三色标记算法,它是用来干嘛的,以及它和 CMS ...
- 一文带你弄懂 Maven 拉包原理
业务需求开发的时候,我们总是会遇到拉不到依赖包的情况.此时如果不清楚 Maven 拉取依赖包的原理,那么很可能找不到问题所在.今天树哥就带大家了解下 Maven 拉包的原理,让你在遇到问题的时候能快速 ...
随机推荐
- k8s中controller-runtime并发Reconcile分析
§ 0x01 起因 开发控制器时,团队内一直在讨论是否需要为单个控制器对象添加并发控制(即加锁),最终把 controller-runtime 框架中并发数改为1,同时启用了 k8s 的 leader ...
- 修改ListCtrl控件列标题文本居左显示
修改ListCtrl控件标题文本居左显示 原来的列标题文本是居中显示: 现在要把列表文本居中改为居左显示 其方法如下: 获得标题控件句柄 g_listCtrl.m_hWnd为ListCtrl控件的句柄 ...
- centos7.5离线安装zabbix4.0
一.配置环境 1.1 Linux环境说明 zabbix 安装要求 https://www.zabbix.com/documentation/4.0/zh/manual/installation/req ...
- Kafka Topic 中明明有可拉取的消息,为什么 poll 不到
开心一刻 今天小学女同学给我发消息她:你现在是毕业了吗我:嗯,今年刚毕业她给我发了一张照片,怀里抱着一只大橘猫她:我的眯眯长这么大了,好看吗我:你把猫挪开点,它挡住了,我看不到她:你是 sb 吗,滚我 ...
- Ubuntu 添加虚拟内存文件
添加交换文件 准备工作 查看当前系统中启用的交换空间(swap space)的详细信息: sudo swapon --show 查看系统的内存和总交换空间的使用情况: free -h 为了有足够的空间 ...
- Windows 设置 FRP 自动启动
由于 frps/frpc 不是 Windows 服务应用程序,因此我们不能直接使用 New-Service 命令创建 frps/frpc 服务.我们可以使用下面的方法将 frps/frpc 封装为 W ...
- dataX是阿里开源的离线数据库同步工具
dataX是阿里开源的离线数据库同步工具的使用 DataX介绍: DataX 是阿里开源的一个异构数据源离线同步工具,致力于实现包括关系型数据库(MySQL.Oracle等).HDFS.Hive.OD ...
- 3.1 gradio的基本使用详解
·gr.Text:用于文本输入,适用于自然语言处理任务的模型. gr.Image:用于图像上传,适用于图像处理或计算机视觉模型. ·gr.Audio:用于音频输入,适用于语音识别或音频处理模型. im ...
- 阿里邮箱网页正常登陆,outlook报错
事件起因: 某客户使用阿里邮箱办公,然又使用outlook绑定阿里邮箱:在网页端可以登录阿里邮箱,但是在outlook的登录的时候,服务器.端口均设置无误,但是就是登录不上去,死活都等登录不上去,总是 ...
- eBPF 概述:第 3 部分:软件开发生态
1. 前言 在本系列的第 1 部分和第 2 部分中,我们对 eBPF 虚拟机进行了简洁的深入研究.阅读上述部分并不是理解第 3 部分的必修课,尽管很好地掌握了低级别的基础知识确实有助于更好地理解高级别 ...