CyclicBarrier
、Semaphore
和 CountDownLatch
是 Java 并发包中用于线程协作的工具类,它们虽然都与线程同步相关,但设计目的和使用场景有显著差异。以下是它们的核心区别和典型应用场景:
1. CountDownLatch
核心机制
- 一次性计数器:初始化时指定一个固定数值(
count
),线程调用countDown()
减少计数器,其他线程通过await()
等待计数器归零。 - 不可重置:计数器归零后无法重复使用。
适用场景
- 主线程等待子线程完成初始化:例如主线程等待所有服务启动后再处理请求。
- 并行任务完成后汇总结果:多个子任务并行执行,主线程等待所有子任务完成后再汇总。
- 模拟并发测试:通过
CountDownLatch
让所有线程同时开始执行。
特点
- 一次性使用:计数器归零后不可重置。
- 无回调机制,仅用于同步等待。
示例
CountDownLatch latch = new CountDownLatch(3);// 子线程完成任务后调用 countDown()
executor.submit(() -> {doTask();latch.countDown();
});// 主线程等待所有子线程完成
latch.await();
2. CyclicBarrier
核心机制
- 可重复使用的屏障:一组线程相互等待,直到所有线程到达屏障点后,再一起继续执行。
- 支持回调:可以指定一个
Runnable
任务,在所有线程到达屏障后触发。
适用场景
- 多阶段任务协作:例如并行计算需要分阶段处理,每个阶段需等待所有线程完成。
- 数据分批处理:多个线程处理数据后,在屏障点合并结果。
- 模拟复杂并发逻辑:如多玩家游戏的回合制同步。
特点
- 可重用:通过
reset()
方法重置计数器。 - 支持屏障后回调函数,用于统一处理阶段结果。
示例
CyclicBarrier barrier = new CyclicBarrier(3, () -> {System.out.println("所有线程到达屏障点");
});executor.submit(() -> {doPhase1();barrier.await(); // 等待其他线程doPhase2();
});
3. Semaphore
核心机制
- 资源访问控制:通过“许可证”机制限制同时访问共享资源的线程数。
- 支持公平/非公平模式:防止线程饥饿。
适用场景
- 资源池管理:如数据库连接池、线程池。
- 限流:控制接口的最大并发请求数。
- 互斥锁扩展:通过
Semaphore(1)
实现类似锁的功能(但更灵活,可跨方法释放)。
特点
- 动态调整:通过
acquire()
和release()
增减许可数。 - 支持超时和中断响应,避免死锁。
示例
Semaphore semaphore = new Semaphore(5); // 允许5个线程同时访问void accessResource() {semaphore.acquire(); // 获取许可try {useResource();} finally {semaphore.release(); // 释放许可}
}
关键区别总结
特性 | CountDownLatch | CyclicBarrier | Semaphore |
---|---|---|---|
重置能力 | 一次性,不可重置 | 可重复使用 | 可重复使用 |
核心目的 | 主线程等待子线程完成特定操作 | 线程相互等待到屏障点 | 控制资源访问的并发数 |
计数器方向 | 递减(countDown() ) | 递增(await() ) | 获取/释放许可证(acquire() /release() ) |
协作关系 | 主线程等待子线程 | 线程间相互等待 | 线程与资源之间的协调 |
是否支持回调 | 否 | 是(到达屏障后触发任务) | 否 |
典型场景 | 主从线程同步 | 分阶段并行任务协同 | 限流、资源池管理 |
如何选择?
- 线程组协同(多阶段) → CyclicBarrier
- 主线程等待子线程完成 → CountDownLatch
- 控制并发访问量或资源池 → Semaphore
- 需重用或动态调整计数器 → CyclicBarrier 或 Semaphore