⭐ 推荐:社区规则条款 V1.0

Java 中 Phaser/CyclicBarrier/CountDownLatch 区别?

chinaz
chinaz 初级会员萌新站长
发布于 2026-10-18 15:48 ·5 浏览 ·0 回复
内容摘要

三者可从计数方向、能否复用和参与者能否动态变化区分:CountDownLatch 一次性递减至归零,CyclicBarrier 可循环等待到齐,Phaser 支持多阶段且参与者可增删,应按实际场景选择。

三者的核心差别就三句话:CountDownLatch 是「一次性、计数递减、等归零」;CyclicBarrier 是「可循环、计数递增、等到齐」;Phaser 是「多阶段、参与者可动态增删」。CountDownLatch 和 CyclicBarrier 都自 JDK 1.5 引入,Phaser 自 JDK 7 引入,是前两者的「合体升级版」,但简单场景用它反而更啰嗦。

CountDownLatch、CyclicBarrier、Phaser 最核心的区别是什么?

结论:判断用哪个,只看三个维度——计数方向、能否复用、参与者数量能否动态变化。

维度CountDownLatchCyclicBarrierPhaser
计数方向构造时给 count,countDown() 递减到 0构造时给 parties,await() 递增到 parties注册 parties,arrive() 递增到 parties
能否复用不能,归零即废,只能新建能,自动进入下一代(generation)能,阶段(phase)自动递增
参与者动态调整不能不能能,register() / arriveAndDeregister()
线程角色等待方与计数方可以不同(主从)所有线程对等,互相等待二者皆可
底层实现AQS 共享模式ReentrantLock + Condition单个 long 型 state 位运算(高 32 位存 phase,低 32 位存 parties)

一句话记忆:CountDownLatch 是「等别人做完」,CyclicBarrier 是「等大家到齐」,Phaser 是「等大家到齐,而且队伍可以换人、可以跑很多轮」。

CountDownLatch 怎么用?为什么它不能复用?

结论:CountDownLatch 适合「一个(或几个)线程等 N 个任务全部完成」的场景,计数一旦归零就永久失效,无法重置。

典型写法是主线程 await(),工作线程在 finally 里 countDown():

CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        try { /* 业务 */ } finally { latch.countDown(); }
    }).start();
}
latch.await();          // 阻塞到计数为 0
// 或 latch.await(5, TimeUnit.SECONDS); 超时返回 false

关键点有两个:一是 countDown() 必须放 finally,否则任务抛异常会导致计数永远到不了 0、await() 永久阻塞;二是 getCount() 只用于监控和调试,不要拿它做业务判断。因为内部 AQS 的 state 只减不增,所以不存在 reset(),这也是它「一次性」的根本原因。顺带一提,如果是「等一组任务结果」而非单纯等完成,JDK 8 的 CompletableFuture.allOf() 通常是更好的选择。

CyclicBarrier 怎么用?和 CountDownLatch 有什么区别?

结论:CyclicBarrier 适合「固定 N 个线程反复汇合、每一轮都到齐才继续」的场景,比如多线程分片计算后统一进入下一轮迭代。

CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("一轮结束"));
Runnable task = () -> {
    for (int round = 0; round < 5; round++) {
        // 本轮计算
        barrier.await();   // 三个线程都到这里才一起放行
    }
};

和 CountDownLatch 最本质的区别是「对等性」:CountDownLatch 里调 countDown() 的线程和调 await() 的线程可以是两拨人,职责不对称;CyclicBarrier 里每个线程都要 await(),谁也不能少,人也必须固定。第二个区别是可循环:一轮结束后自动开启下一代,构造参数里的 Runnable 会在每轮汇合点由最后一个到达的线程执行一次。

坑点:某个线程在 await() 时被中断或超时,屏障会「破损」,其余等待线程收到 BrokenBarrierException;手动调 reset() 同理。所以 await() 必须捕获这两个异常,并考虑是否重建屏障。用 isBroken() 可以查询破损状态。

Phaser 怎么用?什么场景必须用它?

结论:当「阶段数在运行时才能确定」或「参与线程会中途加入/退出」时,Phaser 是唯一选择,CountDownLatch 和 CyclicBarrier 都做不到。

Phaser phaser = new Phaser(1);   // 主线程自己先注册 1 个
for (int i = 0; i < 3; i++) {
    phaser.register();           // 运行时动态增加参与者
    new Thread(() -> {
        phaser.arriveAndAwaitAdvance();  // 阶段 0 汇合
        phaser.arriveAndDeregister();    // 干完活退出,不再参与后续阶段
    }).start();
}
phaser.arriveAndDeregister();    // 主线程注销,剩下的人继续

常用 API 分工明确:arrive() 只到达不等待,arriveAndAwaitAdvance() 到达并等本阶段所有人到齐,arriveAndDeregister() 到达后注销自己。查询用 getPhase()、getRegisteredParties()、getArrivedParties()、getUnarrivedParties()。想控制终止条件就重写 onAdvance(int phase, int registeredParties),返回 true 即终止 Phaser;forceTermination() 可强制结束。

两个注意点:一是 awaitAdvance(int phase) 不响应中断,需要可中断版本请用 awaitAdvanceInterruptibly();二是参数传错阶段号会立即返回,不会阻塞,调试时容易误判。此外 Phaser 支持构造分层结构(new Phaser(parent, parties)),用于海量线程分组汇合,这是 CyclicBarrier 完全没有的能力。

三者该怎么选?给一条判断标准

结论:一个线程等 N 个任务结束用 CountDownLatch;N 个线程反复互等到齐用 CyclicBarrier;阶段数不定或人数会变用 Phaser。

补充两条实践建议。第一,能用高层的 CompletableFuture、ExecutorService.invokeAll() 解决的任务编排,就别手写屏障,可读性更好;第二,所有等待都要带超时(await(timeout, unit)、awaitAdvanceInterruptibly(phase, timeout, unit)),否则一个卡住的任务会让整个线程池挂死。

收束一下:CountDownLatch 是单向的一次性倒计数,CyclicBarrier 是可循环的对等汇合点,Phaser 是把两者能力合并、再加动态注册和分层的重武器。选型先问自己三个问题——谁等谁、跑几轮、人数变不变,答案自然就出来了。

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2267.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 0

还没有回复,来抢沙发~