在并发编程里,经常遇到一类需求:某些线程必须等其他若干线程把前置工作做完才能继续。比如初始化配置、加载字典表、建立连接池,这些都完成后主流程才可以启动。Java并发包提供的CountDownLatch就是专门用来做这种线程依赖控制的同步工具。它内部维护一个计数器,调用await的线程会阻塞,直到计数器变为零,而其他线程完成任务后调用countDown把计数减一。

CountDownLatch的基本用法
CountDownLatch在创建时需要传入一个整数作为初始计数,这个数值代表需要等待的任务数量。每当一个依赖任务结束,就调用一次countDown方法,计数减一。调用await方法的线程会一直阻塞,直到计数归零或者被中断。和Thread.join不同,CountDownLatch不要求等待线程必须是被等待线程的父线程,也不要求被等待线程结束生命周期,只关心计数。
下面是一段最基础的示例,主线程等待两个工作线程初始化完成后再打印启动信息:
import java.util.concurrent.CountDownLatch;
public class BasicDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(2);
new Thread(() -> {
System.out.println("加载配置");
latch.countDown();
}).start();
new Thread(() -> {
System.out.println("建立连接池");
latch.countDown();
}).start();
latch.await();
System.out.println("所有前置任务完成,主服务启动");
}
}
这段代码里,两个子线程分别完成各自准备动作后调用countDown,主线程在await处停下,直到两次countDown执行完,计数变成零,主线程才继续。这种方式比在main里顺序调用两个方法更合理,因为两个准备任务可以并行执行。
用CountDownLatch控制多层依赖
实际项目中依赖关系往往不止一层。例如数据同步平台需要先并行拉取订单、用户、商品三类数据,全部到位后再做关联计算,而关联计算结束又要通知下游导出线程。这种场景可以用两个CountDownLatch串联:第一个Latch等待三个抓取线程,第二个Latch等待计算线程。
示例代码如下,展示了嵌套依赖的控制方式:
import java.util.concurrent.CountDownLatch;
public class MultiStageDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch fetchLatch = new CountDownLatch(3);
CountDownLatch calcLatch = new CountDownLatch(1);
String[] names = {"订单", "用户", "商品"};
for (String name : names) {
new Thread(() -> {
System.out.println("拉取" + name + "数据");
fetchLatch.countDown();
}).start();
}
new Thread(() -> {
try {
fetchLatch.await();
System.out.println("开始关联计算");
calcLatch.countDown();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
calcLatch.await();
System.out.println("通知导出线程生成报表");
}
}
这个结构里,计算线程先等待抓取Latch,完成计算后再释放calcLatch,主线程据此知道全流程完毕。通过多个Latch组合,可以清晰表达“先并行后汇聚”的依赖拓扑,而且每个阶段超时时还能用await(long, TimeUnit)避免永久卡死。
和join、CyclicBarrier的差别
不少初学者会用Thread.join控制顺序,但join只能等线程真正终止,且不支持中途计数。如果线程是池化复用或者需要在运行中途释放等待方,join就无能为力。CountDownLatch则只认计数,不关心线程是否结束,使用更灵活。另一个容易混淆的是CyclicBarrier,它侧重让一组线程互相等到齐再同时继续,且可重复使用;CountDownLatch是一次性消费品,计数到零就失效,不能再重置。
从底层看,CountDownLatch基于AQS的共享模式实现,countDown释放共享锁,await获取共享锁并挂起。因此多个等待线程可以同时被唤醒,适合“一对多通知”模型。下表简要对比三者:
| 工具 | 是否可重用 | 等待目标 | 典型场景 |
|---|---|---|---|
| join | 否 | 线程终止 | 简单顺序执行 |
| CountDownLatch | 否 | 计数归零 | 主线程等前置任务 |
| CyclicBarrier | 是 | 线程到齐 | 多线程同步起点 |
选择工具时要看依赖本质是“等事情做完”还是“等伙伴到齐”。前者用CountDownLatch更直接,后者才考虑CyclicBarrier。
实践中的注意事项
使用CountDownLatch最常见的坑是countDown调用次数少于初始计数,导致await永远不返回。所以务必把countDown放在finally块里,保证异常情况下也能递减。另外,如果等待方设置了超时,要通过返回值判断是真归零还是超时,避免后续逻辑在准备不足时执行。
下面示例演示了带超时和finally的安全写法:
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class SafeDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
new Thread(() -> {
try {
System.out.println("处理业务");
} finally {
latch.countDown();
}
}).start();
if (latch.await(3, TimeUnit.SECONDS)) {
System.out.println("正常完成");
} else {
System.out.println("等待超时,执行降级");
}
}
}
把countDown放进finally,配合带超时的await,可以让依赖控制既准确又具备容错能力。在微服务启动、批量任务编排、测试并发逻辑等场景,CountDownLatch都是低成本且易理解的方案。
CountDownLatch线程依赖并发控制修改时间:2026-08-01 12:54:27