CountDownLatch 与 CyclicBarrier 区别深度解析
CountDownLatch 与 CyclicBarrier 深度解析:并发编程中的“闸门”与“集合点”
在 Java 并发工具包 java.util.concurrent 中,CountDownLatch 与 CyclicBarrier 是最常用的两种线程同步辅助类。它们像是并发世界的两种“交通信号灯”:
-
一个控制“何时开闸”,
-
一个控制“所有人到齐再出发”。
虽然名字相似,但用途与机制大不相同。本文将带你从概念、源码、区别、应用场景到选型策略,全面理解这两个同步工具。
一、前置概念:线程同步的意义
在多线程环境中,各个线程常常需要在某些时刻协调执行顺序。例如:
-
主线程需要等待多个子任务都完成;
-
多个线程需要在每一轮阶段计算后对齐;
-
某个操作需要所有线程同时开始。
传统做法是使用 wait()、notify()、synchronized 等低层机制,但编写复杂且容易出错。
CountDownLatch 与 CyclicBarrier 就是为了解决这种阶段性协调问题的高级抽象。
二、CountDownLatch:一次性“闭锁闸门”
1. 原理简介
CountDownLatch 维护一个初始计数(count)。
-
调用
countDown()会让计数减一; -
当计数减到 0 时,所有在
await()上等待的线程都会被释放。
它就像一道闸门:
-
计数没归零前,门是关闭的;
-
当所有“任务”报告完成后,门一次性打开。
2. 常见用法
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
// 执行子任务
Thread.sleep(200);
System.out.println(Thread.currentThread().getName() + " done");
} catch (Exception ignored) {}
latch.countDown(); // 任务完成后减一
}).start();
}
// 主线程等待所有任务完成
latch.await();
System.out.println("All tasks finished, main thread proceeds");
输出类似:
Thread-0 done
Thread-1 done
Thread-2 done
All tasks finished, main thread proceeds
3. 特点
-
一次性使用:计数到 0 后就不能重置;
-
等待方不限于参与者:主线程、其他线程都可以
await(); -
实现基于 AQS(AbstractQueuedSynchronizer);
-
典型场景:主线程等待多个子任务结束。
三、CyclicBarrier:可循环“集合点”
1. 原理简介
CyclicBarrier 用于让固定数量的线程在每一阶段都相互等待。
只有当所有线程都到达屏障点后,屏障才会放行,让它们同时进入下一阶段。
之所以叫 Cyclic,是因为它可以自动复位,可多次使用。
2. 使用示例
int parties = 3;
CyclicBarrier barrier = new CyclicBarrier(parties,
() -> System.out.println("== 所有人到齐,开始下一阶段 =="));
for (int i = 0; i < parties; i++) {
new Thread(() -> {
try {
stageWork(1);
barrier.await(); // 阶段1集合
stageWork(2);
barrier.await(); // 阶段2集合
stageWork(3); // 最后一阶段
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
void stageWork(int stage) {
System.out.println(Thread.currentThread().getName() + " 执行阶段 " + stage);
Thread.sleep(200 + (int)(Math.random()*300));
}
输出(顺序可能不同):
Thread-1 执行阶段 1
Thread-2 执行阶段 1
Thread-0 执行阶段 1
== 所有人到齐,开始下一阶段 ==
Thread-2 执行阶段 2
Thread-0 执行阶段 2
Thread-1 执行阶段 2
== 所有人到齐,开始下一阶段 ==
Thread-1 执行阶段 3
Thread-2 执行阶段 3
Thread-0 执行阶段 3
每个阶段都要等全部线程到齐后,才能统一进入下一阶段。
3. 特点
-
参与者线程数固定(由构造参数
parties指定); -
到齐自动释放并复位,可多轮循环使用;
-
可设置 barrier action(到齐时触发的回调逻辑);
-
若某线程超时或中断,会导致屏障“破损”(BrokenBarrierException);
-
实现基于 ReentrantLock + Condition。
四、核心区别对比
| 项目 | CountDownLatch | CyclicBarrier |
|---|---|---|
| 用途 | 等待若干事件完成(一次性) | 多线程阶段同步(可重复) |
| 谁在等 | 任意线程 | 参与的所有线程 |
| 是否可复用 | 否,计数归零后失效 | 是,自动复位 |
| 控制逻辑 | 计数递减到 0 开闸 | 所有线程到齐开闸 |
| 回调机制 | 无 | 可设置 barrier action |
| 异常机制 | await() 可被中断 |
可抛 BrokenBarrierException |
| 实现基础 | AQS | ReentrantLock + Condition |
| 常见场景 | 主线程等待子任务完成 | 分阶段并行任务、回合同步 |
口诀记忆:
Latch 等事件结束,一次开闸;Barrier 等人到齐,循环闸门。
五、应用场景举例
✅ CountDownLatch
- 主线程等待多个子任务完成
例如汇总多接口结果、并发加载数据源。
- 系统初始化依赖
启动时等多个组件加载完再对外服务。
- 并发压测
所有线程同时起跑(一个启动门)→ 同时统计完成(一个终点门)。
✅ CyclicBarrier
- 分阶段计算任务
如并行矩阵迭代,每轮计算后需等所有线程同步再进入下一轮。
- 分布式批量对齐
多个节点周期性计算指标,每一批计算完成后再统一写结果。
- 多人游戏/竞赛回合
每个回合所有玩家行动完才统一结算下一回合。
- 多阶段 ETL/清洗任务
各线程并行处理子表,每阶段完成一次汇总检查。
六、源码机制简析
CountDownLatch
内部持有 Sync extends AbstractQueuedSynchronizer:
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public void countDown() {
sync.releaseShared(1);
}
protected int tryAcquireShared(int ignored) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int ignored) {
int c, next;
do { c = getState(); next = c-1; } while (!compareAndSetState(c, next));
return next == 0;
}
状态值 state = 计数,归 0 时释放所有等待者。
CyclicBarrier
public int await() throws InterruptedException, BrokenBarrierException {
lock.lock();
try {
int index = --count;
if (index == 0) { // 最后一个到达
barrierCommand.run(); // 可选回调
nextGeneration(); // 重置 count
trip.signalAll(); // 唤醒所有等待者
} else {
while (index > 0) trip.await();
}
} finally {
lock.unlock();
}
}
每轮结束自动进入下一代(generation),实现循环使用。
七、选型建议
| 需求 | 推荐 |
|---|---|
| 主线程等待多个任务完成 | ✅ CountDownLatch |
| 所有线程都要阶段对齐 | ✅ CyclicBarrier |
| 需要多阶段复用 | ✅ CyclicBarrier |
| 一次性同步(起跑/结束) | ✅ CountDownLatch |
| 参与者动态变化 | ✅ Phaser(更灵活) |
八、总结
-
CountDownLatch像一次性的倒计时闸门:计数归零,一次开闸。 -
CyclicBarrier像可重置的集合点:所有线程集合后一起出发,可多轮使用。 -
二者都是“同步工具”,但并不保证共享数据的线程安全,只控制执行时机。
-
使用时要注意线程超时、中断及异常破坏屏障的情况。
一句话收尾:
CountDownLatch管“一次性等待”,CyclicBarrier管“多阶段对齐”,理解它们的本质,就能优雅地写出同步且高效的并发程序。
浙公网安备 33010602011771号