CountDownLatch 类的作用是什么?请解释其工作原理和适用场景。
参考回答
CountDownLatch
是 Java 并发工具类,用来实现 线程之间的同步。它允许一个或多个线程等待,直到其他线程完成一组操作后再继续执行。
作用:
- 主线程等待多个子线程完成任务。
- 控制某些操作必须在其他操作完成之后再执行。
工作原理:
CountDownLatch
初始化时需要传入一个计数值count
。- 线程通过调用
await()
方法进入等待状态,直到计数值减为 0。 - 每当一个线程完成任务后,调用
countDown()
方法使计数值减 1。 - 当计数值减为 0 时,所有调用
await()
方法等待的线程被唤醒,继续执行。
适用场景:
- 任务分解与汇总:主线程等待所有子线程完成后继续执行。
- 多服务依赖启动:确保主服务在所有依赖服务启动后开始运行。
详细讲解与拓展
1. CountDownLatch
的基本使用
示例代码:
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
int threadCount = 3;
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " is working.");
latch.countDown(); // 每个线程完成后调用 countDown()
}).start();
}
latch.await(); // 主线程等待
System.out.println("All threads have finished. Main thread resumes.");
}
}
输出示例:
Thread-0 is working.
Thread-1 is working.
Thread-2 is working.
All threads have finished. Main thread resumes.
分析:
CountDownLatch
初始值为 3。- 每个子线程完成任务后,调用
countDown()
,将计数值减 1。 - 主线程调用
await()
,直到计数值减为 0,主线程继续执行。
2. CountDownLatch
的工作原理
CountDownLatch
的实现依赖于 AQS(AbstractQueuedSynchronizer):
- 计数器维护:
- 内部通过 AQS 的状态(
state
)保存计数值。 countDown()
:将state
减 1。await()
:当state
为 0 时,唤醒所有等待线程。
- 内部通过 AQS 的状态(
- 线程等待:
- 调用
await()
的线程会被阻塞并加入 AQS 队列,直到计数器变为 0。
- 调用
3. 常见场景
场景 1:任务分解与汇总
描述:
- 将一个任务拆分为多个子任务,主线程等待所有子任务完成后再汇总结果。
示例代码:
import java.util.concurrent.CountDownLatch;
public class TaskDivisionExample {
public static void main(String[] args) throws InterruptedException {
int taskCount = 5;
CountDownLatch latch = new CountDownLatch(taskCount);
for (int i = 0; i < taskCount; i++) {
int taskId = i;
new Thread(() -> {
System.out.println("Task " + taskId + " is completed.");
latch.countDown(); // 子任务完成后减 1
}).start();
}
latch.await(); // 主线程等待所有子任务完成
System.out.println("All tasks are completed. Proceeding to next step.");
}
}
场景 2:服务启动顺序控制
描述:
- 确保主服务在所有依赖服务启动后再启动。
示例代码:
import java.util.concurrent.CountDownLatch;
public class ServiceStartupExample {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
new Thread(() -> {
System.out.println("Service A started.");
latch.countDown();
}).start();
new Thread(() -> {
System.out.println("Service B started.");
latch.countDown();
}).start();
new Thread(() -> {
System.out.println("Service C started.");
latch.countDown();
}).start();
latch.await(); // 等待所有服务启动
System.out.println("Main service started.");
}
}
输出示例:
Service A started.
Service B started.
Service C started.
Main service started.
场景 3:模拟并发测试
描述:
- 模拟多个线程同时发起请求。
示例代码:
import java.util.concurrent.CountDownLatch;
public class ConcurrentTestExample {
public static void main(String[] args) throws InterruptedException {
int threadCount = 5;
CountDownLatch readyLatch = new CountDownLatch(threadCount);
CountDownLatch startLatch = new CountDownLatch(1);
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " is ready.");
readyLatch.countDown();
try {
startLatch.await(); // 等待统一开始信号
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " started.");
}).start();
}
readyLatch.await(); // 等待所有线程准备好
System.out.println("All threads are ready. Start signal sent.");
startLatch.countDown(); // 发送开始信号
}
}
输出示例:
Thread-0 is ready.
Thread-1 is ready.
Thread-2 is ready.
Thread-3 is ready.
Thread-4 is ready.
All threads are ready. Start signal sent.
Thread-0 started.
Thread-1 started.
Thread-2 started.
Thread-3 started.
Thread-4 started.
4. 注意事项
- 一次性使用:
CountDownLatch
不能重置或复用,若需要复用,考虑使用CyclicBarrier
或其他工具类。
- 线程安全性:
countDown()
是线程安全的,可以被多个线程并发调用。
- 计数值设计:
- 初始化时计数值应与任务数量一致,否则可能导致线程永久等待。
总结
特性 | 描述 |
---|---|
作用 | 让一个线程等待多个线程完成操作;或控制线程启动顺序。 |
工作原理 | 使用 AQS 维护计数器状态,计数器减为 0 时,释放所有等待线程。 |
适用场景 | 任务分解与汇总、服务启动顺序控制、并发测试等。 |
优点 | 简化线程之间的同步控制,使用简单,线程安全。 |
注意事项 | 一次性使用,不能重置或复用;计数值需与任务数量一致。 |