一句话总结
三者都是 JUC 的并发工具类,基于 AQS 实现:CountDownLatch — 倒计数门闩,一个线程等待其他线程完成(countDown),一次性,不可重置;CyclicBarrier — 循环栅栏,一组线程互相等待到齐后一起执行(await),可重用,支持回调;Semaphore — 信号量,控制同时访问资源的线程数量(acquire/release),用于限流。
初级理解
CountDownLatch — 倒计数门闩
一个线程(或多个)等待其他线程完成任务,计数减到 0 时释放所有等待线程。
// 场景:主线程等待 3 个子线程全部完成
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
doWork();
latch.countDown(); // 计数 -1
}).start();
}
latch.await(); // 阻塞直到计数为 0
System.out.println("所有子线程完成");
CyclicBarrier — 循环栅栏
一组线程互相等待,所有线程都到达栅栏后一起继续执行。
// 场景:3 个线程都到达后一起出发
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有线程到齐,出发!"); // 回调
});
for (int i = 0; i < 3; i++) {
new Thread(() -> {
prepare();
barrier.await(); // 等待其他线程
runTogether(); // 一起执行
}).start();
}
Semaphore — 信号量
控制同时访问资源的线程数量,用于限流。
// 场景:最多 3 个线程同时访问资源
Semaphore semaphore = new Semaphore(3); // 3 个许可
for (int i = 0; i < 10; i++) {
new Thread(() -> {
semaphore.acquire(); // 获取许可(可能阻塞)
try {
accessResource();
} finally {
semaphore.release(); // 释放许可
}
}).start();
}
一句话总结:CountDownLatch = 等别人完成,CyclicBarrier = 互相等到齐,Semaphore = 限流控制。
中级深入
核心区别对比
| 特性 | CountDownLatch | CyclicBarrier | Semaphore |
| 可重用 | ❌ 一次性 | ✅ 可重置 | ✅ 可释放许可 |
| 计数方向 | 递减(countDown) | 递增(await 计数+1) | 递减(acquire) |
| 回调 | ❌ 无 | ✅ barrierAction | ❌ 无 |
| 等待线程 | 一个等多个 / 多个等一个 | 线程互相等待 | 线程等待许可 |
| AQS 模式 | 共享模式 | ReentrantLock + Condition | 共享模式 |
CountDownLatch 源码分析
// CountDownLatch 基于 AQS 共享模式
public class CountDownLatch {
private final Sync sync;
// await() → AQS.acquireSharedInterruptibly()
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// countDown() → AQS.releaseShared()
public void countDown() {
sync.releaseShared(1);
}
// Sync 的 tryAcquireShared:state == 0 时获取成功
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
// Sync 的 tryReleaseShared:state 减 1,减到 0 时释放
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; // 减到 0 时唤醒所有等待线程
}
}
}
CyclicBarrier 源码分析
// CyclicBarrier 基于 ReentrantLock + Condition
public class CyclicBarrier {
private final ReentrantLock lock = new ReentrantLock();
private final Condition trip = lock.newCondition();
private final int parties; // 参与线程数
private int count; // 当前等待计数
private Runnable barrierCommand;
private int dowait() {
lock.lock();
try {
int index = --count;
if (index == 0) { // 最后一个到达的线程
if (barrierCommand != null)
barrierCommand.run(); // 执行回调
trip.signalAll(); // 唤醒所有等待线程
// 重置 count,实现可重用
count = parties;
return 0;
}
// 不是最后一个,等待
trip.await();
} finally {
lock.unlock();
}
}
}
Semaphore 源码分析
// Semaphore 基于 AQS 共享模式
public class Semaphore {
private final Sync sync;
// acquire() → AQS.acquireSharedInterruptibly()
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// release() → AQS.releaseShared()
public void release() {
sync.releaseShared(1);
}
// 公平模式:先检查队列中是否有等待线程
protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors()) return -1; // 公平检查
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
中级要点:CountDownLatch 和 Semaphore 基于 AQS 共享模式,CyclicBarrier 基于 ReentrantLock + Condition。CyclicBarrier 可重用是因为最后一个线程到达后会重置 count。
高级拓展
CyclicBarrier 的 broken 状态
如果某个线程在 await 时被中断或超时,CyclicBarrier 会进入 broken 状态,其他等待线程会收到 BrokenBarrierException。此时需要调用 reset() 重置。
// CyclicBarrier 的异常处理
try {
barrier.await(5, TimeUnit.SECONDS); // 超时等待
} catch (TimeoutException e) {
// 超时会导致 barrier 进入 broken 状态
barrier.reset(); // 重置
} catch (BrokenBarrierException e) {
// 其他线程导致 broken
}
Semaphore 的公平模式
Semaphore 支持公平和非公平两种模式。公平模式下,acquire 按 FIFO 顺序获取许可;非公平模式下,新来的线程可以"插队"直接 CAS 获取许可。
实际应用场景
| 工具 | 典型场景 |
| CountDownLatch | 并行任务完成后汇总、服务启动依赖检查 |
| CyclicBarrier | 多线程分批计算后合并、游戏多人匹配 |
| Semaphore | 数据库连接池限流、API 限流、停车场管理 |
高级加分项:能说出 CyclicBarrier 的 broken 状态和处理方式,知道 Semaphore 的公平/非公平模式,能根据场景选择合适的工具。
实战场景
场景一:CountDownLatch — 并行查询汇总
public Map<String, Object> parallelQuery() throws Exception {
CountDownLatch latch = new CountDownLatch(3);
Map<String, Object> result = new ConcurrentHashMap<>();
new Thread(() -> {
result.put("user", queryUser());
latch.countDown();
}).start();
new Thread(() -> {
result.put("order", queryOrder());
latch.countDown();
}).start();
new Thread(() -> {
result.put("product", queryProduct());
latch.countDown();
}).start();
latch.await(3, TimeUnit.SECONDS); // 最多等 3 秒
return result;
}
场景二:Semaphore — 简易限流器
public class RateLimiter {
private final Semaphore semaphore;
public RateLimiter(int maxConcurrent) {
this.semaphore = new Semaphore(maxConcurrent);
}
public boolean tryAcquire(long timeout, TimeUnit unit) {
try {
return semaphore.tryAcquire(timeout, unit);
} catch (InterruptedException e) {
return false;
}
}
public void release() {
semaphore.release();
}
}
面试模拟
Q:CountDownLatch 和 CyclicBarrier 的区别?
A:CountDownLatch 是一次性的,计数递减,一个线程等待其他线程完成(或反过来);CyclicBarrier 是可重用的,计数递增,一组线程互相等待到齐后一起执行,支持 barrierAction 回调。CountDownLatch 基于 AQS 共享模式,CyclicBarrier 基于 ReentrantLock + Condition。
Q:Semaphore 的原理是什么?
A:Semaphore 基于 AQS 共享模式,state 表示剩余许可数。acquire() 尝试减少 state,如果 state < 0 则线程入队阻塞;release() 增加 state 并唤醒等待线程。支持公平/非公平模式,常用于限流和资源池管理。
Q:CyclicBarrier 的 broken 状态是什么?
A:当某个线程在 await 时被中断、超时或发生异常,CyclicBarrier 进入 broken 状态。此时其他等待线程会收到 BrokenBarrierException。需要调用 reset() 重置才能继续使用。