CountDownLatch
一个很常见的场景:主线程要等待所有子任务完成后才能继续。
比如:游戏加载时,所有资源都加载完才能开始。
CountDownLatch 就是干这个的。
核心思想
想象一场跑步比赛:
发令枪响(主线程等待)
│
├── 选手1 ──► 准备完毕 ──► 等待
├── 选手2 ──► 准备完毕 ──► 等待
└── 选手3 ──► 准备完毕 ──► 等待
│
└── 所有选手准备完毕 ──► 比赛开始!CountDownLatch 有一个计数器,初始值 = 等待的事件数。
每完成一个事件,countDown() 一次,计数器减 1。
计数器归零时,等待的线程被唤醒。
代码演示
基础用法
java
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
int taskCount = 3;
CountDownLatch latch = new CountDownLatch(taskCount);
System.out.println("主线程:开始执行");
// 创建并启动多个子线程
for (int i = 0; i < taskCount; i++) {
final int taskId = i;
new Thread(() -> {
try {
System.out.println("子任务 " + taskId + " 开始执行");
Thread.sleep((long) (Math.random() * 2000 + 500));
System.out.println("子任务 " + taskId + " 执行完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown(); // 任务完成后计数减 1
}
}).start();
}
// 主线程等待所有子任务完成
System.out.println("主线程:等待子任务完成...");
latch.await();
System.out.println("主线程:所有子任务已完成,继续执行");
System.out.println("主线程:执行完成");
}
}带超时的等待
java
public class CountDownLatchWithTimeout {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
// 启动任务
for (int i = 0; i < 3; i++) {
final int taskId = i;
new Thread(() -> {
try {
Thread.sleep((long) (Math.random() * 3000 + 1000));
System.out.println("任务 " + taskId + " 完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown();
}
}).start();
}
// 等待最多 5 秒
boolean completed = latch.await(5, TimeUnit.SECONDS);
if (completed) {
System.out.println("所有任务在 5 秒内完成");
} else {
System.out.println("等待超时,仍有任务未完成");
}
}
}实际应用:模拟服务启动
java
public class ServiceStartupDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println("服务启动中...");
CountDownLatch latch = new CountDownLatch(3);
// 启动数据库连接池
startComponent("数据库连接池", 2000, latch);
// 启动缓存服务
startComponent("缓存服务", 1500, latch);
// 启动消息队列消费者
startComponent("消息队列消费者", 1000, latch);
// 等待所有组件启动完成
latch.await();
System.out.println("所有组件启动完成,服务已就绪");
}
private static void startComponent(String name, int initTime, CountDownLatch latch) {
new Thread(() -> {
try {
System.out.println(name + " 正在启动...");
Thread.sleep(initTime);
System.out.println(name + " 启动完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown();
}
}, name).start();
}
}实际应用:并行计算
java
public class ParallelComputationDemo {
public static void main(String[] args) throws InterruptedException {
int[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int threadCount = 3;
int chunkSize = (int) Math.ceil((double) data.length / threadCount);
long[] results = new long[threadCount];
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
final int threadId = i;
final int start = i * chunkSize;
final int end = Math.min(start + chunkSize, data.length);
new Thread(() -> {
try {
long sum = 0;
for (int j = start; j < end; j++) {
sum += data[j];
}
results[threadId] = sum;
System.out.println("线程 " + threadId + " 计算完成,和 = " + sum);
} finally {
latch.countDown();
}
}).start();
}
latch.await();
// 汇总结果
long total = Arrays.stream(results).sum();
System.out.println("最终结果:" + total);
}
}方法一览
| 方法 | 说明 |
|---|---|
CountDownLatch(int count) | 构造方法,设置计数器初始值 |
await() | 等待计数器减到 0,可被中断 |
await(long timeout, TimeUnit) | 等待指定时间,返回是否成功 |
countDown() | 计数器减 1 |
getCount() | 获取当前计数器值 |
注意事项
- 一次性使用:CountDownLatch 创建后不能重置,计数减到 0 后不能再使用
- 必须在 finally 中调用:确保无论正常还是异常退出,都执行
countDown() - 计数器初始值:必须大于 0,否则
await()会立即返回 - 线程安全:
countDown()和await()都是线程安全的
java
// ❌ 错误示例:未在 finally 中调用 countDown
new Thread(() -> {
doSomething();
// 如果这里抛出异常,countDown 不会执行
latch.countDown();
}).start();
// ✅ 正确示例
new Thread(() -> {
try {
doSomething();
} finally {
latch.countDown(); // 确保执行
}
}).start();要点回顾
CountDownLatch 适用于"一等多"的场景:
- 主线程等待多个子任务完成
- 服务启动等待多个组件就绪
- 并行计算等待所有分片完成
记住:一次性,不可重用。
