【Java并发工具三剑客】CountDownLatch、CyclicBarrier和Semaphore详解
在Java并发编程中,java.util.concurrent包提供了强大的工具类来简化线程间的协调工作。本文将深入探讨三个核心工具:CountDownLatch、CyclicBarrier和Semaphore,分析它们的原理、应用场景和关键区别,并提供实用的代码示例。
一、核心工具详解
1. CountDownLatch(倒计时闩锁)
原理:基于计数器实现,初始值代表需要等待的事件数。工作线程完成任务后调用countDown()减少计数,主线程通过await()阻塞等待计数器归零。
典型应用场景:
- 主线程等待所有子任务完成
- 服务启动等待依赖资源初始化
- 并行计算任务同步
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
int workerCount = 3;
CountDownLatch latch = new CountDownLatch(workerCount);
for (int i = 0; i < workerCount; i++) {
new Thread(() -> {
System.out.println("工作者" + Thread.currentThread().getId() + "初始化完成");
latch.countDown(); // 计数器减1
}).start();
}
System.out.println("主线程等待初始化...");
latch.await(); // 阻塞直到计数器归零
System.out.println("所有工作者初始化完成,主线程继续");
}
}
/* 输出:
主线程等待初始化...
工作者14初始化完成
工作者13初始化完成
工作者15初始化完成
所有工作者初始化完成,主线程继续 */
2. CyclicBarrier(循环屏障)
原理:让一组线程在屏障点相互等待,当所有线程都到达后执行预设操作并重置屏障,可循环使用。
典型应用场景:
- 多阶段数据处理(加载→处理→存储)
- 并行计算的分步同步
- 多线程测试的并发起点控制
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
int threadCount = 3;
Runnable barrierAction = () -> System.out.println("--- 所有线程到达屏障 ---");
CyclicBarrier barrier = new CyclicBarrier(threadCount, barrierAction);
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " 加载阶段1数据");
barrier.await(); // 第一次等待
System.out.println(Thread.currentThread().getName() + " 处理阶段1数据");
barrier.await(); // 第二次等待(屏障重用)
System.out.println(Thread.currentThread().getName() + " 加载阶段2数据");
} catch (Exception e) {
e.printStackTrace();
}
}, "Worker-"+i).start();
}
}
}
/* 输出:
Worker-0 加载阶段1数据
Worker-1 加载阶段1数据
Worker-2 加载阶段1数据
--- 所有线程到达屏障 ---
Worker-2 处理阶段1数据
Worker-0 处理阶段1数据
Worker-1 处理阶段1数据
--- 所有线程到达屏障 ---
Worker-1 加载阶段2数据
Worker-2 加载阶段2数据
Worker-0 加载阶段2数据 */
3. Semaphore(信号量)
原理:维护一组许可证,控制资源访问并发数。线程通过acquire()获取许可,release()释放许可。
典型应用场景:
- 数据库连接池管理
- API限流控制
- 资源池实现(如线程池)
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
public static void main(String[] args) {
int maxConnections = 3;
Semaphore semaphore = new Semaphore(maxConnections);
for (int i = 1; i <= 5; i++) {
new Thread(() -> {
String threadName = Thread.currentThread().getName();
try {
System.out.println(threadName + " 尝试获取连接");
semaphore.acquire(); // 获取许可
System.out.println(threadName + " 获取连接成功 | 剩余许可: "
+ semaphore.availablePermits());
Thread.sleep(2000); // 模拟数据库操作
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 释放许可
System.out.println(threadName + " 释放连接");
}
}, "Thread-"+i).start();
}
}
}
/* 输出:
Thread-1 尝试获取连接
Thread-2 尝试获取连接
Thread-3 尝试获取连接
Thread-4 尝试获取连接
Thread-5 尝试获取连接
Thread-1 获取连接成功 | 剩余许可: 2
Thread-2 获取连接成功 | 剩余许可: 1
Thread-3 获取连接成功 | 剩余许可: 0
(等待2秒...)
Thread-1 释放连接
Thread-4 获取连接成功 | 剩余许可: 0
Thread-2 释放连接
Thread-5 获取连接成功 | 剩余许可: 0 */
二、核心区别对比
| 特性 | CountDownLatch | CyclicBarrier | Semaphore |
|---|---|---|---|
| 核心目的 | 等待事件完成 | 线程组在屏障点相互等待 | 控制并发访问资源的数量 |
| 计数器 | 递减 (countDown), 一次性 | 递增 (await),可重置循环使用 | 可增减 (acquire/release), 可重用 |
| 重置能力 | 不可重置 | 可循环使用 | 持续管理许可 |
| 触发条件 | 计数器减到 0 | 等待线程数达到 预设值 | 有可用许可 |
| 线程角色 | 主线程(等待) vs 工作线程(做事) | 所有线程角色对等 | 线程角色无特定关系 |
| 屏障动作 | 不支持 | 支持 (可选Runnable) | 不支持 |
| 典型比喻 | 起跑线裁判等待运动员就位 | 旅游团在景点集合点等待团员 | 停车场入口闸机控制车辆进入 |
三、关键区别解析
一次性 vs 循环性:
CountDownLatch是一次性的,计数器归零后即失效CyclicBarrier可循环使用,自动重置计数器Semaphore持续管理许可证,无使用次数限制
等待模式:
CountDownLatch:单向等待(主线程等子线程)CyclicBarrier:多向等待(所有线程相互等待)Semaphore:资源竞争(线程间无直接协调)
计数器行为:
CountDownLatch:只减不增(countDown())CyclicBarrier:内部计数增加到目标值后重置Semaphore:可增可减(acquire()减,release()增)
四、如何选择合适工具
根据实际场景需求选择最合适的工具:
需要 主线程等待多个子任务完成 → 选择
CountDownLatch// 微服务启动等待依赖初始化
CountDownLatch serviceLatch = new CountDownLatch(3);
databaseInit(serviceLatch);
cacheInit(serviceLatch);
configLoad(serviceLatch);
serviceLatch.await(); // 等待所有依赖就绪
startService();
需要 多线程分阶段同步执行 → 选择
CyclicBarrier// 并行计算分阶段处理
CyclicBarrier computeBarrier = new CyclicBarrier(4, () ->
System.out.println("阶段完成,交换中间结果"));
需要 限制资源并发访问量 → 选择
Semaphore// API限流(每秒最多100请求)
Semaphore rateLimiter = new Semaphore(100);
executor.submit(() -> {
rateLimiter.acquire();
callExternalAPI();
rateLimiter.release();
});
五、总结
Java并发工具三剑客各有其适用场景:
- CountDownLatch 是任务协调器,解决"主等子"的同步问题
- CyclicBarrier 是线程同步器,解决"线程组多阶段协同"问题
- Semaphore 是资源控制器,解决"并发访问量限制"问题
理解它们的核心区别和适用场景,能够帮助我们在复杂并发场景中选择最合适的工具,构建高效可靠的并发系统。在实际开发中,根据具体需求灵活选用这些工具,可以显著提升程序的并发性能和可维护性。
【Java并发工具三剑客】CountDownLatch、CyclicBarrier和Semaphore详解的更多相关文章
- java 并发工具类CountDownLatch & CyclicBarrier
一起在java1.5被引入的并发工具类还有CountDownLatch.CyclicBarrier.Semaphore.ConcurrentHashMap和BlockingQueue,它们都存在于ja ...
- Java并发工具类 - CountDownLatch
Java并发工具类 - CountDownLatch 1.简介 CountDownLatch是Java1.5之后引入的Java并发工具类,放在java.util.concurrent包下面 http: ...
- Java并发工具类CountDownLatch源码中的例子
Java并发工具类CountDownLatch源码中的例子 实例一 原文描述 /** * <p><b>Sample usage:</b> Here is a pai ...
- Java中的4个并发工具类 CountDownLatch CyclicBarrier Semaphore Exchanger
在 java.util.concurrent 包中提供了 4 个有用的并发工具类 CountDownLatch 允许一个或多个线程等待其他线程完成操作,课题点 Thread 类的 join() 方法 ...
- 25.大白话说java并发工具类-CountDownLatch,CyclicBarrier,Semaphore,Exchanger
1. 倒计时器CountDownLatch 在多线程协作完成业务功能时,有时候需要等待其他多个线程完成任务之后,主线程才能继续往下执行业务功能,在这种的业务场景下,通常可以使用Thread类的join ...
- Java 并发工具类 CountDownLatch、CyclicBarrier、Semaphore、Exchanger
本文部分摘自<Java 并发编程的艺术> CountDownLatch CountDownLatch 允许一个或多个线程等待其他线程完成操作.假设现有一个需求:我们需要解析一个 Excel ...
- JAVA并发工具类---------------(CountDownLatch和CyclicBarrier)
CountDownLatch是什么 CountDownLatch,英文翻译为倒计时锁存器,是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待. 闭锁可以延迟线程的进 ...
- Java并发编程:线程封闭和ThreadLocal详解
转载请标明出处: http://blog.csdn.net/forezp/article/details/77620769 本文出自方志朋的博客 什么是线程封闭 当访问共享变量时,往往需要加锁来保证数 ...
- Java并发编程--多线程中的join方法详解
Java Thread中, join()方法主要是让调用该方法的thread在完成run方法里面的部分后, 再执行join()方法后面的代码 例如:定义一个People类,run方法是输出姓名年龄. ...
- Java并发编程3-抽象同步队列AQS详解
AQS是AtractQueuedSynchronizer(队列同步器)的简写,是用来构建锁或其他同步组件的基础框架.主要通过一个int类型的state来表示同步状态,内部有一个FIFO的同步队列来实现 ...
随机推荐
- 即时通信SSE和WebSocket对比
Server-Sent Events (SSE) 和 WebSocket 都是用于实现服务器与客户端实时通信的技术,但它们在设计目标.协议特性和适用场景上有显著区别.以下是两者的详细对比: 一.核心区 ...
- 为什么 Java 新生代被划分为 S0、S1 和 Eden 区?
为什么 Java 新生代被划分为 S0.S1 和 Eden 区? 在 Java 的 垃圾回收(GC)机制中,新生代 被进一步划分为 Eden 区 和两个 Survivor 区(S0 和 S1).这种划 ...
- 极客时间上新 .NET + AI 体系课
课程特色 1️⃣ 全网首个.NET+AI体系化课程(没有之一!) 2️⃣ Semantic Kernel + Kernel Memory 核心知识全覆盖 3️⃣ 每课时基于Polyglot Noteb ...
- Vue(六)——条件渲染
Vue--条件渲染 v-if.v-else-if.v-else v-if 指令用于条件性地渲染一块内容,表达式的值为 true --渲染. false--不渲染 v-if.v-else-if.v-el ...
- UnicodeDecodeError: ‘ascii‘ codec can‘t decode byte 0xe8 in position...解决方法
运行python程序,出现了以下错误: File "C:/��/python ѧϰ/god_mellonѧϰpython/untitled2/fofa_py2.py", line ...
- Linux系统的一些基本文件和目录管理命令
pwd:查看当前目录所在位置. ls:查看当前目录下的文件和目录.例如我们查看根目录下的文件和目录: (注:蓝字的是目录,白字的是文件,绿字的是可执行的文件或装有可执行文件的目录,红字是压缩包) 如果 ...
- Manus爆火,我发现平替开源项目OpenManus带你玩转AI智能体开发,无需邀请码!
嗨,大家好,我是小华同学,关注我们获得"最新.最全.最优质"开源项目和高效工作学习方法 "在AI技术日新月异的今天,OpenManus像一把打开智能体开发大门的万能钥匙, ...
- 第二章 Spring Boot 整合 Kafka消息队列 生产者
系列文章目录 第一章 Kafka 配置部署及SASL_PLAINTEXT安全认证 第二章 Spring Boot 整合 Kafka消息队列 生产者 第三章 Spring Boot 整合 Kaf ...
- 【记录】Truenas scale|NFSv4数据集的子目录或文件的ACL完全访问权限继承老是继承不了怎么回事
我遇到了数据集下新建文件夹或文件,新建的文件夹或文件没有和数据集的ACL设置相符合的情况.其根本原因是NFSv4的完全访问权限要想继承的话,它的访问设置权限要设置"用户"和&quo ...
- c++并发编程实战-第2章 线程管控
线程的基本管控 每个应用程序都至少拥有一个线程,即运行main函数的线程,称为主线程,它由c++运行时系统启动.我们可以在软件运行中产生其他线程,它们以指定的函数作为入口函数.当main函数返回后,程 ...