JUC并发包CountDownLatch减法计数器的使用实例(多线程)
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类实例,初始计数为3A 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 () 调用完成
- 输出结果显示等待是否成功
运行结果示例:
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()已调用
主线程继续执行...
或者(如果超时):
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. 并行任务协调
多个线程并行执行子任务,主线程需要等待所有子任务完成后再继续执行。
典型场景:
- 批量数据处理:将大任务拆分为多个子任务并行处理,等待所有子任务完成后汇总结果。
- 系统初始化:多个模块并行初始化,主程序等待所有模块初始化完成后启动服务。
示例代码:
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 初始化完成。
示例代码:
// 主服务等待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. 性能测试与并发模拟
在多线程性能测试中,确保所有线程同时开始执行,或等待所有线程完成后统计结果。
典型场景:
- 压测工具:模拟大量用户同时访问系统。
- 并发算法验证:验证多线程环境下的线程安全问题。
示例代码:
// 启动门:确保所有线程同时开始
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. 分步任务执行
任务分多个阶段执行,每个阶段需要等待前一阶段所有任务完成。
典型场景:
- 数据处理流水线:解析数据 → 清洗数据 → 存储数据,每个阶段并行处理但需按顺序执行。
示例代码:
// 第一阶段完成信号
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,可有效简化多线程协调逻辑,提升系统并发性能。