synchronized 与 CountDownLatch 共存是为兼顾互斥与协作:前者保护共享状态,后者协调线程时序;countDown() 可在锁内执行以保障条件判断安全,await() 应置于锁外避免阻塞锁。

在 synchronized 内部结合 CountDownLatch 做阶段屏障,本质上是混合使用两种不同层级的同步机制:前者用于**临界区互斥访问**(保护共享状态),后者用于**线程协作等待**(协调执行时序)。二者不冲突,但需注意职责分离——synchronized 管“谁能在同一时间改数据”,CountDownLatch 管“谁得等到别人做完才能继续”。直接在 synchronized 块里调用 latch.await() 是安全的,但反过来说,countDown() 通常也建议在 synchronized 块中执行(如果它依赖共享状态判断是否该倒计时)。
为什么需要两者共存?
单纯用 synchronized 无法表达“等全部线程完成某阶段”的语义;单纯用 CountDownLatch 无法保护多线程并发修改的中间状态(比如统计完成数、更新标志位)。典型场景如:
- 多个工作线程并发处理子任务,每完成一个就更新共享计数器并
countDown() - 主线程需在所有子任务提交后、且全部完成前,做一次统一初始化(如预热缓存),这一步必须加锁防止重复或错乱
- 最后统一等待:主线程调用
latch.await(),但这个等待动作本身不需锁,而前面的初始化步骤需要
关键写法:锁内更新 + 锁外等待
推荐结构是:在 synchronized 块中完成「状态变更 + countDown」,而 await() 放在锁外(除非你明确需要等锁释放后再阻塞):
-
countDown()可以且应该放在synchronized块里——如果它依赖被保护的变量(例如“只有当 taskStatus[i] == DONE 才能倒计时”) -
await()一般不放锁内——避免把其他线程卡在锁外干等,降低吞吐;它本身是阻塞操作,不持有锁更合理 - 若
await()前需检查某个受保护的状态(如“是否已开始阶段二”),则先读取该状态进局部变量,再出锁、再 await
一个真实可用的阶段屏障示例
模拟“三阶段启动”:10个 worker 线程准备资源 → 主线程等齐后执行初始化 → 所有线程再一起进入主逻辑:
立即学习“Java免费学习笔记(深入)”;
CountDownLatch readyLatch = new CountDownLatch(10);
Object guard = new Object();
// Worker 线程(每个都执行)
new Thread(() -> {
// ... 加载资源
synchronized (guard) {
System.out.println("Worker done, counting down");
readyLatch.countDown(); // 安全:仅更新 latch,无竞态
}
}).start();
// 主线程
synchronized (guard) {
System.out.println("Main thread: waiting for all workers to register...");
// 注意:这里不 await!只是确保后续操作不被并发干扰
}
try {
readyLatch.await(); // 放在锁外,干净利落
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("All ready — now init shared context");
// 此处可放心做初始化(比如填充 static Map),无需再锁——因为 await 已保证所有 worker 结束了准备阶段
常见误区提醒
以下做法容易出问题:
- 在
synchronized块里调用await()并长期阻塞——会一直占着锁,导致其他线程无法进入临界区,引发假死 - 多个线程在没加锁的情况下直接读写同一个 boolean 标志位来决定是否
countDown()——可能漏触发或重复触发 - 把
CountDownLatch当作可重置工具——它是一次性的;需要循环屏障请换CyclicBarrier


















