核心思想:火箭发射倒计时 🚀
想象一下发射火箭的场景,在按下最终的发射按钮之前,必须有好几个系统同时完成自检,比如:
- 燃料系统检查
- 引擎系统检查
- 导航系统检查
控制中心(主线程)必须等待这3个检查全部报告“正常”后,才能下达“发射”指令。
CountDownLatch
就好比是这个场景中的倒计时计数器。
CountDownLatch latch = new CountDownLatch(3);
- 这等于在控制中心设置了一个初始值为 3 的倒计时器。意味着我们需要等待3个检查任务完成。
latch.await();
(等待)- 控制中心(主线程)调用这个方法,然后就进入等待状态。它会一直在这里被阻塞,直到倒计时器的数字变成 0。
latch.countDown();
(倒数)- 每个检查系统(工作线程)在完成自己的任务后,就调用一次这个方法。
- 每调用一次,倒计时器的数字就减一。
- 当第三个检查系统也调用了
countDown()
后,倒计时器数字变为0,await()
的等待结束,控制中心(主线程)被唤醒,继续执行后续的发射指令。
最简单易懂的代码示例
下面我们就用代码来模拟这个“火箭发射”的场景。
package CoountDownLatch;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SimpleCountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
// 1. 创建一个 CountDownLatch,计数器设置为 3
// 意味着我们需要等待3个任务完成
final CountDownLatch latch = new CountDownLatch(3);
// 创建一个线程池来管理我们的检查任务
ExecutorService executor = Executors.newFixedThreadPool(3);
System.out.println("主控室:准备发射火箭,等待各系统检查...");
// 2. 分配3个检查任务给不同的线程
for (int i = 1; i <= 3; i++) {
final String checkerName = "检查员-" + i;
executor.submit(() -> {
try {
System.out.println("--> [" + checkerName + "] 开始进行系统检查...");
// 模拟检查耗时
Thread.sleep(new Random().nextInt(2000) + 1000); // 随机耗时1-3秒
System.out.println("... [" + checkerName + "] 检查完成,已报告!");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 3. 关键!任务完成,调用 countDown(),计数器减一
latch.countDown();
}
});
}
// 4. 主线程调用 await() 进入等待
// 它会一直阻塞在这里,直到 latch 的计数器变为 0
System.out.println("主控室:所有检查任务已派出,等待报告...");
latch.await();
// --- 当所有检查任务都调用了 countDown() 后,主线程才会从 await() 返回,执行以下代码 ---
System.out.println("主控室:所有系统检查完成!准备发射!");
System.out.println("3... 2... 1... 火箭发射!🚀");
// 关闭线程池
executor.shutdown();
}
}
代码解析
new CountDownLatch(3)
: 设置了一个需要3个“报告”才能继续的门闩。executor.submit(...)
: 我们派出了3个检查员(线程)去并行工作。latch.countDown()
: 这是每个检查员完成工作后必须要做的事——向控制中心报告“我搞定了”。每报告一次,倒计时就减一。latch.await()
: 这是主线程(控制中心)的等待点。它会一直卡在这里,直到收到全部3个“搞定了”的报告。
运行流程分析
- 程序启动,
main
线程打印 “准备发射火箭…”。 - 3个检查员线程被创建并开始并行地执行检查(你会看到3条 “开始进行系统检查…” 的日志)。
main
线程打印 “所有检查任务已派出…” 后,立刻调用latch.await()
并进入阻塞等待。- 在接下来的几秒内,你会看到检查员们随机地、不按顺序地完成他们的工作,并打印 “检查完成,已报告!”。每完成一个,
latch
的计数就减一。 - 当第三个检查员也完成并调用
countDown()
后,latch
的计数变为0。 main
线程的await()
立刻被唤醒,程序继续执行,打印出最终的 “火箭发射!🚀”。
这个模式非常适合一个主线程需要等待多个子任务全部执行完毕后再进行汇总或执行下一步的场景。
流程:
- 定义Latch数量
- 在多线程任务中每次完成就latch.countDown();
- 在主线程中调用latch.await();进入等待,它会一直阻塞在这里,直到 latch 的计数器变为 0
- 当所有检查任务都调用了 countDown() 后,主线程才会从 await() 返回