CountDownLatch
和 CyclicBarrier
都是 Java 并发工具包 (java.util.concurrent
) 中用于线程协调的类,但它们的用途和行为有本质区别。CountDownLatch是一次性的,等待事件完成;而CyclicBarrier是可循环的,用于线程相互等待。CountDownLatch的计数器由外部控制,而CyclicBarrier由线程自己调用await来减少计数。CountDownLatch适用于主线程等待多个子任务完成,而CyclicBarrier适合分阶段的任务,比如多轮计算。CyclicBarrier有屏障损坏的问题,而CountDownLatch一旦到零就不会改变。CyclicBarrier自动重置,而CountDownLatch需要重新创建。
1. 设计目的
CountDownLatch
- 单向等待:一个或多个线程等待其他线程完成一组操作。
- 一次性使用:计数器减到 0 后不可重置。
- 典型场景:主线程等待所有子任务初始化完成后再继续执行。
CyclicBarrier
- 多向等待:一组线程互相等待,直到所有线程都到达某个屏障点(Barrier)。
- 可重复使用:计数器归零后会自动重置(通过
reset()
或自动循环)。 - 典型场景:多线程分阶段处理任务(如多轮游戏回合)。
2. 参与者数量
CountDownLatch
- 固定参与者数:在初始化时指定计数器值(
new CountDownLatch(N)
),后续无法动态调整。 - 被动等待:其他线程调用
countDown()
减少计数,等待线程调用 await()
阻塞。
CyclicBarrier
- 动态参与者数:初始化时指定线程数,但可通过
reset()
重新开始(需处理线程中断问题)。 - 主动同步:所有线程主动调用
await()
等待彼此,直到所有线程到达屏障点。
3. 代码示例
CountDownLatch
CountDownLatch latch = new CountDownLatch(3);
executor.submit(() -> {
doTask();
latch.countDown();
});
latch.await();
CyclicBarrier
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
});
executor.submit(() -> {
doPhase1();
barrier.await();
doPhase2();
});
4. 异常处理
CountDownLatch
- 如果某个线程未调用
countDown()
,主线程可能永远阻塞(需设计超时机制)。
CyclicBarrier
- 如果某个线程在
await()
时被中断或超时,所有等待线程会抛出 BrokenBarrierException
,屏障被标记为“损坏”,需调用 reset()
重置。
5. 适用场景
场景 | CountDownLatch | CyclicBarrier |
---|
主线程等待子任务完成 | ✅ 是 | ❌ 否 |
子线程互相等待 | ❌ 否 | ✅ 是 |
多次重复同步(多阶段任务) | ❌ 否 | ✅ 是 |
动态调整参与者数量 | ❌ 否 | ✅ 是(需手动重置) |
总结
CountDownLatch
是 一对多 的等待机制,用于外部线程(如主线程)等待其他线程完成任务。CyclicBarrier
是 多对多 的同步机制,用于一组线程互相等待,且支持分阶段任务和重复使用。