【结论】godady 补充的 BrokenBarrierException 和「release() 必须放 finally」是这两类线上事故最集中的地方,比 API 名字本身更值得先记住。
【展开】先帮他原文被截断的地方补上,CyclicBarrier 完整形态大概是这样:
for (int i = 0; i < parties; i++) {
final int id = i;
new Thread(() -> {
try {
System.out.println("阶段1-" + id);
barrier.await(); // 第一轮
System.out.println("阶段2-" + id);
barrier.await(); // 第二轮,复用同一个 barrier
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (BrokenBarrierException e) {
// 整轮已破损,直接退出,别继续走后面的业务逻辑
}
}).start();
}
再补一个比 reset() 更常见的坑:线程池里用 CyclicBarrier 容易死锁。池大小必须 ≥ parties,且不能在池内提交「互相等待」的任务——比如 3 线程的池子跑一个循环任务,每个任务要 await() 两次,一旦某个任务占着线程等第三个人,第三个人还在队列里排不上,整池就悬住了。这种情况我会直接换成 Phaser,或者干脆按阶段拆成两次提交,把等待关系交给主线程。
Semaphore 这边同意公平模式的说法,补一点代价:new Semaphore(n, true) 每次放行都要走 FIFO 队列,并发量大时上下文切换明显,吞吐能差一截;所以只有「线程必须按序拿到许可」的场景(售票、抢位)才开公平。另外做限流我一般不用裸 acquire(),而是 tryAcquire(200, MILLISECONDS) 拿不到就快速失败或返回稍后重试,避免请求全堆在许可队列里把 Tomcat 线程耗光:
if (sem.tryAcquire(200, TimeUnit.MILLISECONDS)) {
try { /* 限流区内 */ } finally { sem.release(); }
} else { /* 降级:提示稍后重试 */ }
【结尾】还有个延伸建议:这三个工具解决不了「任务互相依赖 + 动态增减参与者」的场景,真到那一步就该上 Phaser 了,注册/注销参与者比 CyclicBarrier 灵活得多。楼主补齐后半截后确实值得置顶,尤其把 finally 那几句加粗一下。
正式会员
认证极客






