【Java并发工具三剑客】CountDownLatch、CyclicBarrier和Semaphore详解

在Java并发编程中,java.util.concurrent包提供了强大的工具类来简化线程间的协调工作。本文将深入探讨三个核心工具:CountDownLatchCyclicBarrierSemaphore,分析它们的原理、应用场景和关键区别,并提供实用的代码示例。

一、核心工具详解

1. CountDownLatch(倒计时闩锁)

原理 :基于计数器实现,初始值代表需要等待的事件数。工作线程完成任务后调用countDown()减少计数,主线程通过await()阻塞等待计数器归零。

典型应用场景

  • 主线程等待所有子任务完成
  • 服务启动等待依赖资源初始化
  • 并行计算任务同步
java 复制代码
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(循环屏障)

原理:让一组线程在屏障点相互等待,当所有线程都到达后执行预设操作并重置屏障,可循环使用。

典型应用场景

  • 多阶段数据处理(加载→处理→存储)
  • 并行计算的分步同步
  • 多线程测试的并发起点控制
java 复制代码
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限流控制
  • 资源池实现(如线程池)
java 复制代码
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) ❌ 不支持
典型比喻 起跑线裁判等待运动员就位 旅游团在景点集合点等待团员 停车场入口闸机控制车辆进入

三、关键区别解析

  1. 一次性 vs 循环性

    • CountDownLatch一次性的,计数器归零后即失效
    • CyclicBarrier可循环使用,自动重置计数器
    • Semaphore持续管理许可证,无使用次数限制
  2. 等待模式

    • CountDownLatch单向等待(主线程等子线程)
    • CyclicBarrier多向等待(所有线程相互等待)
    • Semaphore资源竞争(线程间无直接协调)
  3. 计数器行为

    • CountDownLatch:只减不增(countDown()
    • CyclicBarrier:内部计数增加到目标值后重置
    • Semaphore:可增可减(acquire()减,release()增)

四、如何选择合适工具

根据实际场景需求选择最合适的工具:

  • 需要 主线程等待多个子任务完成 → 选择 CountDownLatch

    java 复制代码
    // 微服务启动等待依赖初始化
    CountDownLatch serviceLatch = new CountDownLatch(3);
    databaseInit(serviceLatch);
    cacheInit(serviceLatch);
    configLoad(serviceLatch);
    serviceLatch.await(); // 等待所有依赖就绪
    startService();
  • 需要 多线程分阶段同步执行 → 选择 CyclicBarrier

    java 复制代码
    // 并行计算分阶段处理
    CyclicBarrier computeBarrier = new CyclicBarrier(4, () -> 
        System.out.println("阶段完成,交换中间结果"));
  • 需要 限制资源并发访问量 → 选择 Semaphore

    java 复制代码
    // API限流(每秒最多100请求)
    Semaphore rateLimiter = new Semaphore(100);
    executor.submit(() -> {
        rateLimiter.acquire();
        callExternalAPI();
        rateLimiter.release();
    });

五、总结

Java并发工具三剑客各有其适用场景:

  • CountDownLatch任务协调器,解决"主等子"的同步问题
  • CyclicBarrier线程同步器,解决"线程组多阶段协同"问题
  • Semaphore资源控制器,解决"并发访问量限制"问题

理解它们的核心区别和适用场景,能够帮助我们在复杂并发场景中选择最合适的工具,构建高效可靠的并发系统。在实际开发中,根据具体需求灵活选用这些工具,可以显著提升程序的并发性能和可维护性。

相关推荐
如来神掌十八式7 个月前
Semaphore控制接口并发频率
java·semaphore
说淑人9 个月前
Java & Lock & CountDownLatch & 总结
java·lock·countdownlatch
小小工匠10 个月前
J.U.C Review - 常见的通信工具类解析
juc·countdownlatch·exchanger·phaser·semaphore·cyclicbarrier
中草药z1 年前
【JavaEE】Callable,Semaphore和CountDownLatch
java·开发语言·高并发·多线程·countdownlatch·callable·semaphore
羌俊恩1 年前
Ansible可视化管理之web界面集成使用探究(未完待续)
ansible·web ui·semaphore
莫等闲,白了少年头2 年前
使用CountdownLatch和线程池批量处理http请求,并处理响应数据
java·spring boot·线程池·countdownlatch·批量请求http接口
不能再留遗憾了2 年前
【JavaEE】JUC(Java.util.concurrent)常见类
java·java-ee·多线程·reentrantlock·信号量·countdownlatch