在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 */
复制代码 二、核心区别对比
特性CountDownLatchCyclicBarrierSemaphore核心目的等待事件完成线程组在屏障点相互等待控制并发访问资源的数量计数器递减 (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 是资源控制器,解决"并发访问量限制"问题
理解它们的核心区别和适用场景,能够帮助我们在复杂并发场景中选择最合适的工具,构建高效可靠的并发系统。在实际开发中,根据具体需求灵活选用这些工具,可以显著提升程序的并发性能和可维护性。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |