CyclicBarrier的核心作用是让一组线程互相等待直至全部到达屏障点才集体继续执行;其通过await()减计数器,非最后线程阻塞挂起,最后线程触发屏障动作并唤醒所有线程,支持可重用与异常中断处理。

CyclicBarrier 的核心作用不是“让线程集合”,而是让一组线程互相等待,直到全部到达某个点才集体继续执行——它不控制线程启动时机,只控制阻塞与释放时机。
为什么调用 await() 后线程会阻塞
每个线程调用 CyclicBarrier.await() 时,会尝试将内部计数器减一;若减完后计数器不为 0,当前线程就被挂起并加入等待队列。只有当最后一个线程调用 await() 导致计数器归零,所有等待线程才会被唤醒、同时继续执行。
- 阻塞是线程级的,不是任务级的:即使你用
ExecutorService提交多个Runnable,每个Runnable内仍需显式调用await() - 如果线程在等待时被中断,会抛出
InterruptedException,且屏障会被打破(后续调用await()将直接抛出BrokenBarrierException) -
await()是可重入的,但同一实例不能由同一个线程多次调用(除非重置),否则会死锁
如何正确初始化和复用 CyclicBarrier
构造时传入参与线程总数,可选地指定一个 Runnable 作为“屏障动作”——它会在最后一线程到达后、所有线程被释放前执行,且由该线程独占执行(其他线程仍在阻塞中)。
- 屏障动作适合做汇总、日志或状态检查,**不能耗时或阻塞**,否则拖慢全体线程释放
- 调用
reset()可强行重置屏障(清空等待队列、恢复计数),但正在等待的线程会收到BrokenBarrierException;更安全的做法是新建实例 - 如果不确定线程数,不要用
CyclicBarrier;改用CountDownLatch或协调逻辑前置
final CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有线程已就位,执行屏障动作");
});
常见错误:线程没真正“一起”等,或者根本没等
典型现象是部分线程飞快执行完,另一些卡住不动,甚至出现 BrokenBarrierException 或 InterruptedException。原因往往不在 CyclicBarrier 本身,而在使用方式。
- 线程未全部调用
await():比如某个分支提前 return,或异常跳过,导致计数器永远无法归零 - 线程启动不同步:主线程创建完线程就结束,子线程还没来得及运行
await(),主线程就退出了 - 超时设置不合理:
await(long timeout, TimeUnit unit)返回false表示超时,此时屏障自动打破,后续调用全抛异常 - 混淆
CyclicBarrier和CountDownLatch:前者是“大家齐头并进”,后者是“等别人做完我再动”
最易被忽略的一点:CyclicBarrier 不保证线程执行顺序,也不提供内存可见性保障。如果屏障动作或之后的代码依赖某线程写入的变量,记得加 volatile 或同步块——屏障本身只同步执行点,不传播数据变更。










