孤舟笔记 并发篇三十二 CountDownLatch和CyclicBarrier有什么区别?别再搞混了
文章目录
- 一、先说结论:核心区别一览
- 二、CountDownLatch:一个人等大家
- 三、CyclicBarrier:大家互等,到齐了一起走
- 四、核心区别:能不能互换?
- 五、异常处理的差异
- CountDownLatch vs CyclicBarrier 全景
- 回答技巧与点评
- 标准回答
- 加分回答
- 面试官点评
个人网站
面试常问一道题:“CountDownLatch 和 CyclicBarrier 有什么区别?“很多人背了"一个是倒计数,一个是循环屏障”,但再追问"什么时候用哪个”、“能不能互换”、“CyclicBarrier 的屏障动作是什么”,就支支吾吾了。
今天咱们把这两个同步工具彻底对比清楚,以后再也不搞混。
一、先说结论:核心区别一览
| 维度 | CountDownLatch | CyclicBarrier |
|---|---|---|
| 语义 | 等待 N 个事件完成 | 等待 N 个线程到齐 |
| 使用者 | 1 个线程等 N 个 | N 个线程互相等 |
| 计数方式 | countDown() 减 1,await() 等待 | await() 到齐后自动归零 |
| 是否可重用 | ❌ 一次性,不可重置 | ✅ 自动重置,可循环使用 |
| 异常影响 | 某个线程异常不影响其他 | 某个线程异常会破坏屏障 |
| 回调 | 无 | 支持 barrierAction |
一句话记住:CountDownLatch 是"等人干完活",CyclicBarrier 是"等人到齐了一起走"。
二、CountDownLatch:一个人等大家
场景:主线程需要等待所有子任务完成后才继续执行。
CountDownLatchlatch=newCountDownLatch(3);// 计数 3// 三个子线程executor.submit(()->{loadData();latch.countDown();// 计数 -1 👈});executor.submit(()->{loadConfig();latch.countDown();// 计数 -1 👈});executor.submit(()->{initCache();latch.countDown();// 计数 -1 👈});latch.await();// 主线程等计数归零 👈System.out.println("所有初始化完成,开始业务");生活类比:就像聚餐——你是组织者(主线程),等三拨人都到了才开席。每拨人到了给你发个消息(countDown),你一直等着(await),直到三拨人都到齐。
关键特点:
- 等待者和被等待者不是同一拨人——主线程等子线程
- 子线程只管 countDown,不需要 await
- 一次性——用完就不能再用了
三、CyclicBarrier:大家互等,到齐了一起走
场景:多个线程需要互相等待,全部到齐后一起继续。
CyclicBarrierbarrier=newCyclicBarrier(3,()->{System.out.println("所有选手就位,比赛开始!");// barrierAction 👈});// 三个选手for(inti=0;i<3;i++){newThread(()->{System.out.println(Thread.currentThread().getName()+" 准备好了");barrier.await();// 等其他选手 👈System.out.println(Thread.currentThread().getName()+" 起跑!");}).start();}生活类比:就像赛跑——所有选手到达起跑线后,裁判鸣枪(barrierAction),大家一起跑。跑完一圈,重新集合,再来一圈。
关键特点:
- 等待者就是参与者——大家互相等
- barrierAction——到齐后自动执行的回调
- 可循环——到齐后自动重置,下一轮继续用
四、核心区别:能不能互换?
场景一:只能用 CountDownLatch
// 主线程等 10 个 HTTP 请求完成// 请求方不需要等别人,只管 countDownCountDownLatchlatch=newCountDownLatch(10);for(inti=0;i<10;i++){executor.submit(()->{httpGet(url);latch.countDown();// 请求方不管别人 👈});}latch.await();// 主线程等为什么不能用 CyclicBarrier?HTTP 请求方是"干完就走",不需要等别人——而 CyclicBarrier 要求所有参与者都 await。
场景二:只能用 CyclicBarrier
// 多轮计算:每轮所有线程算完才能进入下一轮CyclicBarrierbarrier=newCyclicBarrier(N);for(intround=0;round<10;round++){for(inti=0;i<N;i++){executor.submit(()->{compute();barrier.await();// 每轮都要同步 👈});}}为什么不能用 CountDownLatch?需要循环使用,CountDownLatch 是一次性的。
场景三:两者都可以
// 3 个线程全部完成后执行汇总// CountDownLatch 版本CountDownLatchlatch=newCountDownLatch(3);// 3 个线程 countDown + 1 个线程 await// CyclicBarrier 版本CyclicBarrierbarrier=newCyclicBarrier(3,()->summary());// 3 个线程 await,barrierAction 做汇总五、异常处理的差异
| 异常场景 | CountDownLatch | CyclicBarrier |
|---|---|---|
| 某个线程异常 | countDown 少调一次,await 可能永远阻塞 | 屏障被破坏,抛出 BrokenBarrierException |
| 超时 | await(timeout) 超时返回 false | await(timeout) 超时抛 TimeoutException |
| 重置 | 不支持 | reset() 重新开始 |
CyclicBarrier 的屏障一旦被破坏(某个线程离开 await),所有等待线程都会收到 BrokenBarrierException。
try{barrier.await();}catch(BrokenBarrierExceptione){// 有人退出了,大家散了吧 👈}catch(InterruptedExceptione){// 被中断了}CountDownLatch vs CyclicBarrier 全景
CountDownLatch vs CyclicBarrier 全景 核心区别 ├── 等待模型 ── 1等N vs N互等 ├── 可重用性 ── 一次性 vs 可循环 ├── 回调 ── 无 vs barrierAction └── 异常 ── 阻塞风险 vs 屏障破坏 CountDownLatch 适用 ├── 主线程等待子任务完成 ├── 一次性场景(启动、关闭) └── 被等待者不需要互相感知 CyclicBarrier 适用 ├── 多线程分阶段计算 ├── 需要循环同步 └── 需要在同步点执行汇总动作 口诀:CountDown 一人等大家,用完即弃不可复, CyclicBarrier 互等齐,循环使用带回调, 异常处理各不同,选错工具会死锁。回答技巧与点评
标准回答
CountDownLatch 和 CyclicBarrier 都是同步工具,核心区别有三:第一,等待模型不同——CountDownLatch 是"一个线程等待 N 个事件完成",CyclicBarrier 是"N 个线程互相等待到齐";第二,可重用性不同——CountDownLatch 是一次性的,CyclicBarrier 自动重置可循环使用;第三,CyclicBarrier 支持 barrierAction,到齐后自动执行回调。选择时,等待者和参与者不是同一拨人用 CountDownLatch,多线程互相等待且需要循环同步用 CyclicBarrier。
加分回答
- 设计模式:CountDownLatch 是"门闩模式"——门关着等所有闩打开;CyclicBarrier 是"栅栏模式"——所有人到齐后一起翻过栅栏。两者都是 AQS 的变体实现,但 CountDownLatch 基于共享模式(AQS 的 state),CyclicBarrier 基于锁+ Condition(ReentrantLock + Condition)
- Phaser 的优势:Java 7 引入了 Phaser,可以看作 CyclicBarrier 的增强版——支持动态注册/注销参与者、多阶段同步、分层。当参与者数量不确定或需要多阶段同步时,Phaser 比 CyclicBarrier 更灵活
- CountDownLatch 的计数问题:如果某个线程异常退出没有 countDown,await 会永远阻塞。解决方案:用 await(timeout) 代替 await,或者用 ExecutorService + invokeAll 来保证所有任务完成
面试官点评
这道题考的是你对并发同步工具的理解和选型能力。能说出"1等N vs N互等、一次性 vs 可循环"是基本要求,能给出具体场景说明什么时候用哪个,才算及格。如果你能提到 Phaser、AQS 底层实现差异、异常处理策略,面试官会认为你对并发工具的理解不只在 API 层面,而是深入到了设计层面。
原文阅读
内容有帮助?点赞、收藏、关注三连!评论区等你 💪
