CountDownLatch
是 Java 并发包(java.util.concurrent
)中的一个同步工具类,用于协调多个线程之间的执行顺序,其核心原理是通过一个计数器控制线程的等待与唤醒。
核心原理
-
初始化计数器
创建CountDownLatch
实例时,需要指定一个初始计数器值(count
),该值通常代表需要等待的任务数量或事件数量。
例如:CountDownLatch latch = new CountDownLatch(3);
表示需要等待 3 个事件完成。 -
线程等待(
await()
)
当线程调用latch.await()
方法时,会检查计数器的值:- 如果计数器 > 0,当前线程会进入阻塞状态,并加入到
CountDownLatch
内部的等待队列中,释放 CPU 资源。 - 如果计数器 == 0,线程会直接继续执行,不阻塞。
- 如果计数器 > 0,当前线程会进入阻塞状态,并加入到
-
计数器递减(
countDown()
)
当某个任务或事件完成时,线程调用latch.countDown()
方法,将计数器的值减 1。
当计数器的值减到 0 时,CountDownLatch
会唤醒所有因调用await()
而阻塞的线程,让它们继续执行。 -
不可重置性
计数器一旦减到 0,就无法再被重置(与CyclicBarrier
不同),即CountDownLatch
只能使用一次。
内部实现细节
CountDownLatch
的底层依赖 AQS(AbstractQueuedSynchronizer,抽象队列同步器) 实现,AQS 是 Java 并发工具的基础框架,用于管理线程的等待队列和状态同步。
CountDownLatch
内部定义了一个继承自 AQS 的同步器(Sync
),将 AQS 的state
变量复用为计数器的值。await()
方法对应 AQS 的共享式获取同步状态:当state > 0
时,线程加入等待队列并阻塞。countDown()
方法对应 AQS 的共享式释放同步状态:每次调用将state
减 1,当state == 0
时,唤醒所有等待线程。
典型使用场景
- 等待多个子任务完成:例如主线程等待所有子线程执行完毕后再汇总结果(如你提供的代码中,等待所有异步计算任务完成)。
- 协调线程执行顺序:例如让多个线程等待某个 “开始信号”(计数器初始为 1,主线程调用
countDown()
后所有线程同时启动)。
示例流程
// 1. 初始化计数器为 2(需要等待 2 个任务)
CountDownLatch latch = new CountDownLatch(2);
// 线程 1:执行任务1
new Thread(() -> {
System.out.println("任务1执行中...");
latch.countDown(); // 计数器减1(变为1)
}).start();
// 线程 2:执行任务2
new Thread(() -> {
System.out.println("任务2执行中...");
latch.countDown(); // 计数器减1(变为0,唤醒等待线程)
}).start();
// 主线程:等待两个任务完成
latch.await(); // 计数器为0时,此处不再阻塞
System.out.println("所有任务完成,主线程继续执行");
输出结果:
任务1执行中...
任务2执行中...
所有任务完成,主线程继续执行
总结:CountDownLatch
通过计数器的增减实现线程间的同步,核心是 “等待方” 阻塞至 “计数器归零”,适用于一次性的多线程协作场景。