外观
一句话答案
CountDownLatch 一次性递减等多任务完成(一个等多个),CyclicBarrier 可复用让多线程互相等待到齐(多个互等)。
核心要点
CountDownLatch(倒计时门闩):
java
// 场景:主线程等待 N 个子任务全部完成后再继续
CountDownLatch latch = new CountDownLatch(3); // 计数器 = 3
// 子任务完成后调用 countDown()
executor.submit(() -> { doTask1(); latch.countDown(); });
executor.submit(() -> { doTask2(); latch.countDown(); });
executor.submit(() -> { doTask3(); latch.countDown(); });
latch.await(); // 主线程阻塞,直到计数器减到 0
System.out.println("所有子任务完成");
// 特点:一次性的,计数器减到 0 后不能重置
// 典型场景:启动时等待多个服务初始化完成、并行查询多个数据源后汇总结果CyclicBarrier(循环栅栏):
java
// 场景:N 个线程互相等待,全部到达栅栏后同时继续
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有线程到达栅栏,执行汇总逻辑");
});
for (int i = 0; i < 3; i++) {
executor.submit(() -> {
doPartialWork();
barrier.await(); // 每个线程在这里等待,直到 3 个线程都到达
doNextPhase(); // 所有线程同时继续
});
}
// 特点:可重用(自动重置),适合多阶段并行计算
// 典型场景:多线程分阶段计算(如 MapReduce 的每轮迭代)Semaphore(信号量):
java
// 场景:控制同时访问某个资源的线程数(限流)
Semaphore semaphore = new Semaphore(5); // 最多允许 5 个线程同时执行
for (int i = 0; i < 20; i++) {
executor.submit(() -> {
semaphore.acquire(); // 获取许可(如果 5 个许可都被占用,阻塞等待)
try {
accessDatabase(); // 最多 5 个线程同时执行这段代码
} finally {
semaphore.release(); // 释放许可
}
});
}
// 典型场景:数据库连接池限流、接口并发数限制三者对比:
| 维度 | CountDownLatch | CyclicBarrier | Semaphore |
|---|---|---|---|
| 等待方式 | 一个线程等待其他 N 个线程 | N 个线程互相等待 | 控制并发数量 |
| 是否可重用 | 否(一次性) | 是(自动重置) | 是 |
| 计数器方向 | 递减到 0 触发 | 递增到 N 触发 | 获取/释放许可 |
| 底层实现 | AQS(共享锁) | ReentrantLock + Condition | AQS(共享锁) |
| 典型场景 | 主线程等子任务完成 | 多阶段并行计算 | 资源并发数限制 |
追问与易错
追问方向:
- CountDownLatch 能重置吗?
- CyclicBarrier 的 barrierAction 在哪个线程执行?
- 什么场景用哪个?
易错点:
- ❌ 混淆两者——CDL 一个等多个,CB 多个互等
- ❌ CountDownLatch 的 countDown 只能调一次——可以多次
💡 记忆锚点
CountDownLatch 是倒计时发令枪:裁判等所有运动员就位(countDown 到 0)再鸣枪(await 放行),用完就扔。CyclicBarrier 是旅游团集合点:所有人到齐了一起出发,到下一个景点再集合——可以反复用。Semaphore 是停车场:只有 N 个车位,满了就在门口等。