多线程同步一直是高并发编程中的核心痛点——如何让多个线程按指定节奏执行?如何等待所有子任务完成后再执行主任务?如何让多个线程同时到达同一节点后再继续?JUC 提供的 CountDownLatch 和 CyclicBarrier,正是解决这两类问题的两大神器。两者都基于 AQS(AbstractQueuedSynchronizer)实现,但设计理念、核心用法与底层实现截然不同:一个是“一次性计数器”,主打“等待其他线程”;一个是“循环屏障”,主打“线程互相等待”。
这篇文章从核心差异切入,再到源码精读、核心流程拆解,最后延伸到实战选型与避坑指引,力求直击要点,不绕弯子。

废话不多说,直接上干货。
一、核心开篇:先搞懂「核心差异」,避免概念混淆
CountDownLatch 和 CyclicBarrier 经常被初学者混淆,核心原因是两者都能实现多线程等待。但从设计初衷、核心特性、使用方式来看,有本质区别。先通过一张核心差异表直击本质:
- CountDownLatch:一次性倒计时门闩,基于 AQS 共享模式,线程之间是“单向等待”关系(一方等另一方),计数归零后不可重置。
- CyclicBarrier:循环屏障,基于 ReentrantLock + Condition,线程之间是“双向/多向等待”关系(互相等待),计数可以循环重置。
一句话总结:两者的核心差异,本质是“等待的主体关系”不同。CountDownLatch 是“单向等待”——主体分离,所以计数完成后无需重置;CyclicBarrier 是“双向/多向等待”——主体合一,所以需要支持循环,满足多次同步的场景。这种设计差异,直接决定了底层实现与实战选型。
二、核心铺垫:AQS 共享模式核心要点(按需聚焦,不冗余)
CountDownLatch 直接基于 AQS 共享模式实现,CyclicBarrier 底层的 ReentrantLock/Condition 也依赖 AQS,因此先快速梳理 AQS 共享模式的三个核心要点:
- AQS 核心状态:volatile int state,所有同步逻辑围绕 state 的修改展开,volatile 保证可见性,CAS 保证原子性。
- 共享模式核心:多个线程可同时获取共享锁,当 state 满足条件时,所有等待线程被依次唤醒,而非互斥唤醒,适配“多线程等待同一条件”的场景。
- 核心方法:共享模式下需重写 tryAcquireShared(int arg)(尝试获取共享锁,返回正数表示成功,0 表示成功但无剩余资源,负数表示失败)和 tryReleaseShared(int arg)(尝试释放共享锁,返回 boolean 表示是否成功),底层通过 CAS 修改 state。
简单说:CountDownLatch 直接将 AQS 的 state 作为自己的倒计时计数器,所有核心逻辑都是对 state 的 CAS 修改和共享模式的等待/唤醒。
三、CountDownLatch 源码解析:一次性倒计时门闩,基于 AQS 共享模式
CountDownLatch 的设计极其简洁:初始化 state 为 N → 线程调用 countDown() 让 state-- → 等待线程调用 await() 等待 state=0 → state=0 时唤醒所有等待线程。全程一次性,state=0 后所有操作失效。
1. 核心结构:极简设计,仅封装 AQS
CountDownLatch 通过内部类 Sync 继承 AQS,重写共享模式的核心方法,将 AQS 的 state 作为倒计时计数器。源码(JDK8 精简版)如下:
public class CountDownLatch {
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) { setState(count); }
int getCount() { return getState(); }
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0) return false;
int nextc = c - 1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
private final Sync sync;
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
}
核心结构总结:CountDownLatch 是 AQS 共享模式的极简封装,无任何额外逻辑——直接将 AQS 的 state 作为倒计时计数器,初始化赋值为 N;重写 tryAcquireShared 判断是否计数归零,重写 tryReleaseShared 实现 CAS 递减 state,仅当 state=0 时才释放成功,触发唤醒。
2. 核心操作:2 个方法拆解,全程围绕 AQS state
(1) countDown():递减计数器,CAS 修改 AQS state
核心作用:让当前线程将倒计时计数减 1,当计数减到 0 时,自动唤醒所有调用 await() 等待的线程。可被任意线程调用,多次调用也无副作用(state=0 后调用无效)。
public void countDown() {
sync.releaseShared(1); // 委托给 AQS releaseShared,底层调用重写的 tryReleaseShared
}
核心流程:线程调用 countDown() → 触发 AQS releaseShared(1) → 底层调用重写的 tryReleaseShared(1),自旋 CAS 将 state 减 1。若减 1 后 state ≠ 0,直接返回;若减 1 后 state = 0,返回 true,触发 AQS 共享模式唤醒逻辑,唤醒同步队列中所有等待线程。
(2) await():等待计数归零,共享模式等待
核心作用:让当前线程进入等待状态,直到 state=0,或线程被中断。可被一个/多个线程调用,实现多线程同时等待。
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
核心流程:线程调用 await() → 触发 AQS acquireSharedInterruptibly(1) → 底层调用 tryAcquireShared(1) 判断 state 是否为 0:若为 0 则获取成功,线程继续执行;若不为 0,则获取失败,将当前线程封装为共享模式节点加入 AQS 同步队列,进入阻塞状态,待其他线程调用 countDown() 使 state=0 后被唤醒。
感悟:CountDownLatch 的设计完美体现了“极简封装 AQS”的思想——它没有新增任何复杂逻辑,只根据“倒计时等待”的需求,重写了 AQS 共享模式的两个核心方法,将 AQS 的 state 作为计数器,利用 AQS 成熟的同步队列和等待/唤醒机制,实现了多线程的单向等待。
四、CyclicBarrier 源码解析:循环屏障,基于 ReentrantLock+Condition
CyclicBarrier 的设计比 CountDownLatch 稍复杂,不是直接重写 AQS 方法,而是基于 ReentrantLock(重入锁)+ Condition(条件队列)实现。核心是“内置计数器 + 条件队列等待”,支持循环使用的关键在于:计数器在所有线程通过屏障后,会自动重置为初始值,且条件队列可重复利用。
1. 核心结构:无 AQS 重写,基于锁 + 条件队列
CyclicBarrier 核心围绕“内置计数器 + 重入锁 + 条件队列”展开,还有屏障任务(所有线程到达屏障后可执行的任务)。源码(JDK8 精简版)如下:
public class CyclicBarrier {
private static class Generation {
boolean broken = false;
}
private final ReentrantLock lock = new ReentrantLock();
private final Condition trip = lock.newCondition();
private final int parties;
private final Runnable barrierCommand;
private Generation generation = new Generation();
private int count;
public CyclicBarrier(int parties) { this(parties, null); }
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
}
核心结构总结:CyclicBarrier 核心是 parties(固定参与线程数)+ count(动态计数器),count 初始等于 parties,每个线程调用 await() 则 count--。ReentrantLock 保证 count 修改原子性,Condition 实现线程条件等待。Generation 代次 + broken 标记是支持循环与异常处理的关键——每次屏障通过/破裂,生成新的 Generation,重置 count 和 broken。
2. 核心操作:await() 是核心,实现等待 + 屏障通过 + 循环
await() 将 countDown 逻辑内置其中——每个线程到达屏障点后调用 await(),自动让 count--,并判断是否到达屏障条件(count=0)。同时集成了等待、屏障通过、重置、异常处理所有逻辑。核心方法 dowait 源码(精简):
private int dowait(boolean timed, long nanos) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
final Generation g = generation;
if (g.broken) throw new BrokenBarrierException();
if (Thread.interrupted()) { breakBarrier(); throw new InterruptedException(); }
int index = --count;
if (index == 0) {
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null) command.run();
ranAction = true;
nextGeneration();
return 0;
} finally {
if (!ranAction) breakBarrier();
}
}
for (;;) {
try {
if (!timed) trip.await();
else if (nanos > 0L) nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
if (g == generation && !g.broken) { breakBarrier(); throw ie; }
else Thread.currentThread().interrupt();
}
if (g.broken) throw new BrokenBarrierException();
if (g != generation) return index;
if (timed && nanos <= 0L) { breakBarrier(); throw new TimeoutException(); }
}
} finally { lock.unlock(); }
}
核心流程分四步:
- 获取锁 + 异常校验:线程调用 await() 先获取重入锁,校验屏障是否破裂、线程是否被中断。
- 计数器递减:count 减 1,返回当前线程的等待索引 index。
- 判断是否触发屏障:若 index=0(最后一个线程到达),执行屏障任务(若有),调用 nextGeneration() 生成新代次——重置 count=parties、唤醒条件队列所有等待线程、创建新 Generation;若 index≠0,进入 Condition 条件队列等待。
- 等待唤醒后处理:被唤醒后校验屏障状态——若屏障通过(新代次),返回索引继续;若屏障破裂/超时/中断,抛异常。
3. 核心辅助方法:实现循环 + 异常处理
- nextGeneration():循环的核心,唤醒所有等待线程,重置 count=parties,创建新的 Generation,清除 broken 标记。
- breakBarrier():异常处理的核心,标记 broken=true,重置 count=parties,唤醒所有等待线程,让它们抛出 BrokenBarrierException,避免永久阻塞。
感悟:CyclicBarrier 的设计体现了“基于基础锁机制做上层封装”的思想——利用 ReentrantLock 和 Condition 这两个已有的同步工具,封装出“线程互相等待”的屏障逻辑,并通过“代次”实现循环,通过“broken 标记”实现异常处理,让同步逻辑更贴合业务场景。
五、核心对比:底层实现与核心特性的深度关联
通过前两节的源码解析,再回头看两者的核心差异,会发现“特性差异源于底层实现差异”,这是理解两者的关键,也是面试常考的问题。总结如下:
| 对比维度 | CountDownLatch | CyclicBarrier |
|---|---|---|
| 底层实现 | 基于 AQS 共享模式,state 做计数器 | 基于 ReentrantLock + Condition |
| 等待关系 | 单向等待(一方等另一方) | 双向/多向等待(互相等待) |
| 计数器重置 | 不可重置,一次性 | 可重置,循环使用 |
| 核心方法 | countDown() + await() 分离 | await() 内含 countDown 逻辑 |
| 屏障任务 | 无 | 可选,所有线程到达后执行 |
| 异常处理 | InterruptedException | InterruptedException + BrokenBarrierException + TimeoutException |
| 适用场景 | 主线程等待多个子任务完成 | 多个线程互相等待,达到同一状态后再同时执行 |
六、场景化延伸 + 实战指引(落地可执行)
1. 场景化延伸:设计思想迁移到业务开发
- CountDownLatch 的“单向等待”思想适用于主任务等待多个子任务完成的场景:如分布式任务调度中主节点等待所有子节点执行完任务后汇总结果;电商订单中主流程等待支付、库存、物流等子流程完成后更新订单状态。
- CyclicBarrier 的“互相等待”思想适用于多个业务流程互相等待,达到同一状态后再同时执行的场景:如电商大促中商品上架、库存锁定、优惠券发放等流程需等待所有流程准备完成后同时开放购买;分布式计算中多个计算节点等待所有节点加载完数据后再同时开始计算。
2. 实战指引:精准选型 + 避坑技巧 + 最优实践
(1) 精准选型
- 用 CountDownLatch:当一个/多个线程等待其他线程完成,且只需一次同步时——如主线程等待所有子线程执行完任务、接口调用等待多个异步接口返回结果。
- 用 CyclicBarrier:当需要多个线程互相等待,到达同一节点后再同时执行,且需要多次同步时——如多线程循环执行任务(每次执行前等待所有线程准备就绪)、定时任务中多个线程等待所有线程到达执行时间后再同时执行。
(2) 实战避坑技巧
CountDownLatch 3 大高频坑:
- 坑1:初始化计数 N 与实际子线程数不一致——如 N=5 但只有 4 个子线程调用 countDown(),导致等待线程永久阻塞。解决方案:严格保证“初始化 N = 实际需要等待的子线程数/操作数”,且所有子线程必须执行 countDown()(放在 finally 中)。
- 坑2:使用无超时 await() 导致永久阻塞——子线程因死锁、异常等未调用 countDown()。解决方案:优先使用带超时的 await(long timeout, TimeUnit unit),超时后做降级处理。
- 坑3:重复使用 CountDownLatch——计数归零后再次调用 await() 会直接返回,无等待效果。解决方案:若需多次同步,改用 CyclicBarrier 或重新创建实例。
CyclicBarrier 3 大高频坑:
- 坑1:参与线程数 parties 与实际调用 await() 的线程数不一致——如 parties=5 但只有 4 个线程调用 await(),导致永久等待。解决方案:严格保证 parties 与实际线程数一致,且所有线程必须执行 await()(放在 finally 中)。
- 坑2:忽略屏障破裂的异常处理——线程中断/超时导致屏障破裂,若未捕获 BrokenBarrierException 会导致业务逻辑异常。解决方案:调用 await() 时必须捕获 BrokenBarrierException,并在异常中做屏障重置 (reset())、触发告警、重新执行等处理。
- 坑3:屏障任务执行耗时过长——屏障任务由最后一个线程执行,若耗时过长会导致其他线程被唤醒后仍需等待。解决方案:屏障任务尽量轻量,耗时任务单独开启线程执行。
(3) 最优实践
- 所有 countDown()/await() 放在 finally 中,避免异常导致未执行同步方法而引发永久阻塞。
- 优先使用带超时的等待方法,设置合理超时时间。
- CountDownLatch 结合线程池使用,让子线程通过线程池管理,避免资源耗尽,同时便于统一管理 countDown() 调用。
- CyclicBarrier 手动重置需谨慎:reset() 会强制重置屏障,唤醒所有等待线程并抛 BrokenBarrierException,仅当屏障破裂后需要重新同步时才使用。
- 异常处理要全面:捕获 InterruptedException、BrokenBarrierException、TimeoutException,并做对应的降级/告警/重试处理,保证程序健壮性。
