高 进阶
CountDownLatch与CyclicBarrier#
一句话答案#
CountDownLatch 一次性递减等多任务完成(一个等多个),CyclicBarrier 可复用让多线程互相等待到齐(多个互等)。
核心要点
CountDownLatch(倒计时门闩):
// 场景:主线程等待 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 后不能重置
// 典型场景:启动时等待多个服务初始化完成、并行查询多个数据源后汇总结果javaCyclicBarrier(循环栅栏):
// 场景:N 个线程互相等待,全部到达栅栏后同时继续
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有线程到达栅栏,执行汇总逻辑");
});
for (int i = 0; i < 3; i++) {
executor.submit(() -> {
doPartialWork();
barrier.await(); // 每个线程在这里等待,直到 3 个线程都到达
doNextPhase(); // 所有线程同时继续
});
}
// 特点:可重用(自动重置),适合多阶段并行计算
// 典型场景:多线程分阶段计算(如 MapReduce 的每轮迭代)javaSemaphore(信号量):
// 场景:控制同时访问某个资源的线程数(限流)
Semaphore semaphore = new Semaphore(5); // 最多允许 5 个线程同时执行
for (int i = 0; i < 20; i++) {
executor.submit(() -> {
semaphore.acquire(); // 获取许可(如果 5 个许可都被占用,阻塞等待)
try {
accessDatabase(); // 最多 5 个线程同时执行这段代码
} finally {
semaphore.release(); // 释放许可
}
});
}
// 典型场景:数据库连接池限流、接口并发数限制java三者对比:
| 维度 | CountDownLatch | CyclicBarrier | Semaphore |
|---|---|---|---|
| 等待方式 | 一个线程等待其他 N 个线程 | N 个线程互相等待 | 控制并发数量 |
| 是否可重用 | 否(一次性) | 是(自动重置) | 是 |
| 计数器方向 | 递减到 0 触发 | 递增到 N 触发 | 获取/释放许可 |
| 底层实现 | AQS(共享锁) | ReentrantLock + Condition | AQS(共享锁) |
| 典型场景 | 主线程等子任务完成 | 多阶段并行计算 | 资源并发数限制 |
面试回答(2分钟版)
CountDownLatch 和 CyclicBarrier 都是并发协调工具,但使用场景不同。CountDownLatch 是一次性的倒计时门闩,典型场景是主线程等待多个子任务全部完成后再继续,比如启动时等多个服务初始化或并行查询多个数据源后汇总结果。它的计数器只能递减不能重置,countDown 减到零后 await 放行。CyclicBarrier 是可复用的循环栅栏,典型场景是多线程分阶段并行计算,所有线程到达栅栏后同时继续下一阶段,到达后计数器自动重置,适合多轮迭代场景。底层实现上,CountDownLatch 基于 AQS 的共享锁模式,CyclicBarrier 基于 ReentrantLock 加 Condition。简单记:CountDownLatch 是一个等多个,用完即废;CyclicBarrier 是多个互相等,可以反复使用。另外 Semaphore 用于控制并发数量限流,三者各有分工。
追问与易错
追问方向:
- “CountDownLatch 能重置吗?”→ 不能,计数器减到 0 后无法重置,是一次性工具;需要重复使用的场景应选 CyclicBarrier
- “CyclicBarrier 的 barrierAction 在哪个线程执行?”→ 在最后一个到达栅栏的线程中执行,不是新线程也不是主线程
- “什么场景用哪个?”→ 主线程等子任务全部完成用 CountDownLatch,多线程分阶段互等用 CyclicBarrier,控制并发资源数用 Semaphore
易错点:
- ❌ 混淆两者——CDL 一个等多个,CB 多个互等
- ❌ CountDownLatch 的 countDown 只能调一次——可以多次