java
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
// A类:定义CountDownLatch
class A {
private final CountDownLatch latch;
public A(int count) {
this.latch = new CountDownLatch(count);
}
public CountDownLatch getLatch() {
return latch;
}
}
// B类:调用countDown()方法
class B {
private final A a;
public B(A a) {
this.a = a;
}
public void countB() {
System.out.println(Thread.currentThread().getName() + " 执行countB()方法,准备调用countDown()");
a.getLatch().countDown();
System.out.println(Thread.currentThread().getName() + " 已调用countDown(),剩余计数: " + a.getLatch().getCount());
}
}
// C类:调用await()方法阻塞当前线程
class C {
private final A a;
public C(A a) {
this.a = a;
}
public void awaitC() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + " 执行awaitC()方法,开始等待...");
// 等待,最多等待5秒
boolean result = a.getLatch().await(5, TimeUnit.SECONDS);
if (result) {
System.out.println(Thread.currentThread().getName() + " 等待完成,所有countDown()已调用");
} else {
System.out.println(Thread.currentThread().getName() + " 等待超时,仍有countDown()未调用");
}
}
}
// 主类:演示CountDownLatch的使用
public class Main {
public static void main(String[] args) {
// 创建A类实例,初始计数为3
A a = new A(3);
// 创建B类和C类实例
B b = new B(a);
C c = new C(a);
// 创建并启动3个线程来调用B类的countB()方法
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
// 模拟一些工作
Thread.sleep((long) (Math.random() * 3000));
b.countB();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "Thread-B-" + i).start();
}
// 主线程调用C类的awaitC()方法等待
try {
c.awaitC();
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
a.getLatch().countDown();
}
System.out.println("主线程继续执行...");
}
}
代码说明:
-
A 类:
- 包含一个 CountDownLatch 实例,通过构造函数初始化计数
- 提供 getLatch () 方法供其他类访问 CountDownLatch
-
B 类:
- 持有 A 类的引用
- countB () 方法调用 CountDownLatch 的 countDown () 方法
-
C 类:
- 持有 A 类的引用
- awaitC () 方法调用 CountDownLatch 的 await (long timeout, TimeUnit unit) 方法,最多等待 5 秒
-
Main 类:
- 创建 A 类实例,初始计数为 3
- 创建 B 类和 C 类实例
- 启动 3 个线程分别调用 B 类的 countB () 方法
- 主线程调用 C 类的 awaitC () 方法等待所有 countDown () 调用完成
- 输出结果显示等待是否成功
运行结果示例:
java
Thread-B-0 执行countB()方法,准备调用countDown()
Thread-B-0 已调用countDown(),剩余计数: 2
Thread-B-1 执行countB()方法,准备调用countDown()
Thread-B-1 已调用countDown(),剩余计数: 1
Thread-B-2 执行countB()方法,准备调用countDown()
Thread-B-2 已调用countDown(),剩余计数: 0
main 执行awaitC()方法,开始等待...
main 等待完成,所有countDown()已调用
主线程继续执行...
或者(如果超时):
java
main 执行awaitC()方法,开始等待...
Thread-B-0 执行countB()方法,准备调用countDown()
Thread-B-0 已调用countDown(),剩余计数: 2
main 等待超时,仍有countDown()未调用
主线程继续执行...
Thread-B-1 执行countB()方法,准备调用countDown()
Thread-B-1 已调用countDown(),剩余计数: 1
Thread-B-2 执行countB()方法,准备调用countDown()
Thread-B-2 已调用countDown(),剩余计数: 0
在实际项目中,CountDownLatch 是一种强大的同步工具,常用于以下场景:
1. 并行任务协调
多个线程并行执行子任务,主线程需要等待所有子任务完成后再继续执行。
典型场景:
- 批量数据处理:将大任务拆分为多个子任务并行处理,等待所有子任务完成后汇总结果。
- 系统初始化:多个模块并行初始化,主程序等待所有模块初始化完成后启动服务。
示例代码:
java
CountDownLatch latch = new CountDownLatch(3);
// 启动3个线程并行执行任务
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
// 执行子任务
processTask();
} finally {
latch.countDown(); // 任务完成,计数减1
}
}).start();
}
// 主线程等待所有子任务完成
latch.await();
System.out.println("所有任务已完成");
2. 资源初始化与依赖等待
确保某些关键资源(如配置文件、数据库连接、网络服务)初始化完成后,其他线程才能继续执行。
典型场景:
- 分布式系统启动:等待所有节点就绪后开始通信。
- 多服务依赖:微服务架构中,服务 A 依赖服务 B 和 C,需等待 B 和 C 初始化完成。
示例代码:
java
// 主服务等待3个依赖服务初始化
CountDownLatch serviceLatch = new CountDownLatch(3);
// 启动3个线程分别初始化服务
new Thread(() -> { initServiceA(); serviceLatch.countDown(); }).start();
new Thread(() -> { initServiceB(); serviceLatch.countDown(); }).start();
new Thread(() -> { initServiceC(); serviceLatch.countDown(); }).start();
// 主服务等待所有依赖初始化完成
serviceLatch.await();
startMainService();
3. 性能测试与并发模拟
在多线程性能测试中,确保所有线程同时开始执行,或等待所有线程完成后统计结果。
典型场景:
- 压测工具:模拟大量用户同时访问系统。
- 并发算法验证:验证多线程环境下的线程安全问题。
示例代码:
java
// 启动门:确保所有线程同时开始
CountDownLatch startGate = new CountDownLatch(1);
// 结束门:统计所有线程完成时间
CountDownLatch endGate = new CountDownLatch(10);
// 创建10个工作线程
for (int i = 0; i < 10; i++) {
new Thread(() -> {
startGate.await(); // 等待统一开始信号
try {
executeTask();
} finally {
endGate.countDown();
}
}).start();
}
// 发出开始信号
startGate.countDown();
// 等待所有线程完成
endGate.await();
System.out.println("所有线程执行完毕");
4. 分步任务执行
任务分多个阶段执行,每个阶段需要等待前一阶段所有任务完成。
典型场景:
- 数据处理流水线:解析数据 → 清洗数据 → 存储数据,每个阶段并行处理但需按顺序执行。
示例代码:
java
// 第一阶段完成信号
CountDownLatch phase1Latch = new CountDownLatch(5);
// 第二阶段完成信号
CountDownLatch phase2Latch = new CountDownLatch(5);
// 第一阶段:并行解析数据
for (int i = 0; i < 5; i++) {
new Thread(() -> {
parseData();
phase1Latch.countDown();
}).start();
}
// 等待第一阶段完成
phase1Latch.await();
// 第二阶段:并行处理数据
for (int i = 0; i < 5; i++) {
new Thread(() -> {
processData();
phase2Latch.countDown();
}).start();
}
// 等待第二阶段完成
phase2Latch.await();
System.out.println("所有阶段完成");
5. 替代 join () 方法
与 Thread.join()
相比,CountDownLatch 更灵活:
- 可在多个线程中调用
countDown()
。 - 支持超时等待(
await(timeout, unit)
)。 - 可重复使用(通过重新创建 CountDownLatch 实例)。
注意事项:
- 避免重复使用 :CountDownLatch 计数为 0 后无法重置,如需循环使用可考虑
CyclicBarrier
。 - 异常处理 :确保在
finally
块中调用countDown()
,防止任务异常导致计数无法归零。 - 性能考量:高并发场景下,大量线程等待可能导致上下文切换开销,需谨慎设计。
通过合理使用 CountDownLatch,可有效简化多线程协调逻辑,提升系统并发性能。