该用 CyclicBarrier 而不是 CountDownLatch 的场景是多个线程需反复同步到达某点再共同推进,如分批处理、多轮迭代或并发压测;因其可重用、支持屏障动作、能响应中断与超时,而 CountDownLatch 仅一次性使用。

什么时候该用 CyclicBarrier 而不是 CountDownLatch
当多个线程需要反复「等彼此都到达某一点」再一起往下走,就该选 CyclicBarrier。比如多线程分批处理数据、并行计算中每轮迭代都需同步、压力测试里模拟并发用户同时发起请求——这些场景下,你不是只等一次,而是可能循环多次。
CountDownLatch 是一次性门闩,倒数完就永久打开;CyclicBarrier 是可重置的旋转门,每次 await() 都会卡住,直到指定数量的线程都调用了它,才集体放行。
- 如果你需要「复用」同一个屏障(比如 10 轮计算,每轮都等 4 个线程齐活),
CyclicBarrier天然支持reset()或自动复用,CountDownLatch得新建实例 -
CyclicBarrier允许设置一个Runnable作为“屏障动作”,在最后第 N 个线程到达时、放行前执行(比如汇总本轮结果),CountDownLatch没这能力 - 注意:如果某个线程在
await()时被中断,整个屏障会被打破,其他等待线程会抛出BrokenBarrierException——这不是 bug,是设计使然,得主动捕获处理
await() 调用后线程到底卡在哪、等什么
await() 不是简单“睡一会”,它会让当前线程阻塞在屏障点,直到满足两个条件之一:指定数量的线程都调用了 await(),或者超时/被中断/屏障被破坏。
关键点在于:所有参与线程必须各自调用一次 await(),才算“到达”。漏调、多调、或某线程根本没进这个方法,都会导致其余线程永远卡住(除非设了超时)。
立即学习“Java免费学习笔记(深入)”;
- 默认无超时的
await()会一直等,生产环境强烈建议用带超时的版本:await(long timeout, TimeUnit unit) - 返回值是
int:表示当前线程是第几个到达的(从 0 开始计数),可用于做“第一个到的线程干点初始化”的逻辑 - 如果某线程在等待时被
interrupt(),它会立即抛出InterruptedException,且屏障状态变为 broken,后续所有await()都会立刻抛BrokenBarrierException
怎么安全地处理 BrokenBarrierException
这个异常不是偶然出错,而是屏障被强制失效后的明确信号——可能因为有人中断、有人超时、或有人调用了 reset()。忽略它,往往意味着后续逻辑还在假定“大家已同步完成”,结果数据错乱。
典型错误写法是只 catch InterruptedException,却把 BrokenBarrierException 往上扔,或者干脆不 catch。
- 必须显式 catch
BrokenBarrierException,并在其中判断是否要重试、清理资源或退出任务 - 如果想重用屏障,得先调用
getNumberWaiting()看还有谁卡着,再决定是否reset();但注意reset()会唤醒所有等待线程并让它们抛出BrokenBarrierException - 常见坑:在屏障动作(
Runnable)里抛异常,也会导致屏障 broken,且该异常会包装成RuntimeException再次触发BrokenBarrierException
实际写法:一个带超时和屏障动作的最小可用例子
下面这段代码模拟 3 个线程协作完成一轮计算:
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有线程到齐,开始汇总");
// 这里可以更新共享状态、写日志、触发下一步
});
// 每个线程里:
try {
Thread.sleep(100); // 模拟不同耗时
System.out.println(Thread.currentThread().getName() + " 到达屏障");
barrier.await(5, TimeUnit.SECONDS); // 带超时,避免死等
} catch (TimeoutException e) {
System.err.println("等不到其他人,超时退出");
} catch (BrokenBarrierException e) {
System.err.println("屏障已损坏,可能有人中断或出错");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
注意 barrier.await(5, TimeUnit.SECONDS) 的超时是「单次等待」上限,不是整个循环的总耗时;而屏障动作里的逻辑,只会在最后一次线程到达时执行一次——这点容易误以为每轮都跑多次。
真正难的从来不是调用 API,而是预判哪个线程可能失败、怎么不让一个线程拖垮整组、以及 broken 之后要不要重建屏障——这些没写在 Javadoc 里,但每天都在线上发生。










