在Java并发编程场景中,经常需要让一个或多个线程等待其他线程完成操作后再继续执行,CountDownLatch就是专门用来解决这类线程协同问题的工具类,它位于java.util.concurrent包下,基于AQS(抽象队列同步器)实现,通过计数器的方式控制线程的阻塞和唤醒。

CountDownLatch核心概念
CountDownLatch内部维护了一个计数器,初始化时指定计数器的初始值。当调用countDown()方法时,计数器的值会减1;当调用await()方法的线程会阻塞,直到计数器的值减到0时,所有阻塞在await()方法上的线程都会被唤醒继续执行。
它的核心构造方法和常用方法如下:
- 构造方法:
CountDownLatch(int count),参数count是计数器的初始值,必须大于等于0 - countDown():将计数器的值减1,如果减到0,会唤醒所有等待的线程
- await():使当前线程阻塞,直到计数器的值变为0,或者线程被中断
- await(long timeout, TimeUnit unit):使当前线程阻塞指定时间,如果时间内计数器变为0则唤醒,否则超时后继续执行
- getCount():返回当前计数器的值
CountDownLatch使用步骤
使用CountDownLatch控制线程协同通常分为三步:
- 创建CountDownLatch实例,指定初始计数器值,这个值通常对应需要等待完成的线程数量或者需要完成的任务数量
- 在需要等待其他线程的线程中调用
await()方法,让当前线程进入阻塞状态 - 在其他线程完成对应任务后调用
countDown()方法,每次调用计数器减1,当计数器归零时,阻塞的线程会被唤醒
典型使用场景与代码示例
场景一:等待所有子线程完成任务后主线程再继续
比如我们需要启动3个子线程分别处理不同的任务,所有子线程都处理完成后,主线程再进行结果汇总,就可以用CountDownLatch实现。
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountDownLatchDemo1 {
public static void main(String[] args) throws InterruptedException {
// 初始计数器为3,对应3个子线程
CountDownLatch latch = new CountDownLatch(3);
ExecutorService threadPool = Executors.newFixedThreadPool(3);
// 提交3个任务到线程池
for (int i = 1; i <= 3; i++) {
int taskId = i;
threadPool.submit(() -> {
try {
System.out.println("子线程" + taskId + "开始处理任务");
// 模拟任务处理耗时
Thread.sleep(1000);
System.out.println("子线程" + taskId + "任务处理完成");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 任务完成,计数器减1
latch.countDown();
}
});
}
System.out.println("主线程等待所有子线程完成任务");
// 主线程阻塞,等待计数器归零
latch.await();
System.out.println("所有子线程任务完成,主线程开始汇总结果");
threadPool.shutdown();
}
}
运行上述代码,输出顺序会是:主线程先打印等待信息,然后3个子线程依次开始处理任务,完成后主线程才会打印汇总信息,不会出现主线程先于子线程完成任务的情况。
场景二:控制多个线程同时开始执行
有时候我们需要让多个线程在准备完成后,同时开始执行某个操作,比如模拟并发请求的场景,可以先让所有线程准备好,然后通过CountDownLatch统一释放。
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo2 {
public static void main(String[] args) throws InterruptedException {
// 计数器初始为1,用来控制所有线程同时开始
CountDownLatch startLatch = new CountDownLatch(1);
int threadNum = 5;
for (int i = 1; i <= threadNum; i++) {
int threadId = i;
new Thread(() -> {
try {
System.out.println("线程" + threadId + "准备完成,等待开始信号");
// 线程阻塞,等待开始信号
startLatch.await();
System.out.println("线程" + threadId + "开始执行任务,当前时间:" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
// 主线程休眠1秒,确保所有子线程都进入等待状态
Thread.sleep(1000);
System.out.println("所有线程准备完毕,发送开始信号");
// 计数器减1归零,所有等待的线程同时被唤醒
startLatch.countDown();
}
}
运行代码可以看到,所有线程的准备完成信息先打印,然后几乎在同一时间打印开始执行的信息,实现了线程同时启动的效果。
使用注意事项
- CountDownLatch的计数器只能使用一次,当计数器归零后,再调用
await()方法不会阻塞线程,也无法重置计数器的值。如果需要重复使用计数器,可以考虑使用CyclicBarrier。 - 调用
countDown()方法的次数不能超过初始计数器的值,否则计数器会变成负数,不过这种情况不会影响await()的唤醒逻辑,因为计数器到0时就已经唤醒所有等待线程了。 - 如果等待的线程被中断,
await()方法会抛出InterruptedException,需要根据业务场景处理中断逻辑,避免线程异常终止。 - 在finally块中调用
countDown()方法,确保即使任务执行出现异常,计数器也能正常减1,避免等待线程永久阻塞。
底层实现原理
CountDownLatch的底层是基于AQS实现的,AQS内部维护了一个state状态变量,CountDownLatch将state作为计数器的值。初始化时,state被设置为传入的count值;调用countDown()方法时,会通过CAS操作将state减1;调用await()方法时,会判断state是否为0,如果不为0,当前线程会被封装成节点加入AQS的等待队列中阻塞;当state减到0时,AQS会唤醒等待队列中的所有节点对应的线程。
这种实现方式保证了计数器的修改是线程安全的,同时等待线程的阻塞和唤醒逻辑也由AQS统一管理,保证了并发场景下的正确性。
CountDownLatchJava并发线程协同多线程协作修改时间:2026-07-08 04:54:24