悠悠楠杉
在Java中如何使用CountDownLatch等待多线程完成
在Java的并发编程世界中,我们常常需要协调多个线程之间的执行顺序。比如,主线程需要等待若干个子任务全部完成后才能继续执行;或者一组工作线程必须在所有准备工作就绪后才开始运行。面对这类场景,java.util.concurrent.CountDownLatch 提供了一个简洁而高效的解决方案。
CountDownLatch 是一个同步辅助类,它允许一个或多个线程等待其他线程完成一系列操作后再继续执行。它的核心机制基于一个计数器,这个计数器在初始化时被设定为某个正整数值。每当一个线程完成了自己的任务,就会调用 countDown() 方法将计数器减一。而那些需要等待的线程则调用 await() 方法进行阻塞,直到计数器归零,所有等待的线程才会被唤醒并继续执行。
我们来看一个典型的使用场景:假设我们要开发一个性能测试工具,需要同时启动10个线程去请求某个服务接口,并统计它们全部完成所需的时间。这时,我们可以使用 CountDownLatch 来确保主线程能准确地等待所有请求线程结束。
java
import java.util.concurrent.CountDownLatch;
public class PerformanceTest {
public static void main(String[] args) throws InterruptedException {
int threadCount = 10;
CountDownLatch latch = new CountDownLatch(threadCount);
long startTime = System.currentTimeMillis();
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
try {
// 模拟请求操作
Thread.sleep((long) (Math.random() * 2000));
System.out.println(Thread.currentThread().getName() + " 完成请求");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown(); // 任务完成,计数器减一
}
}).start();
}
latch.await(); // 主线程等待,直到计数器为0
long endTime = System.currentTimeMillis();
System.out.println("所有线程执行完毕,总耗时:" + (endTime - startTime) + " 毫秒");
}
}
在这个例子中,CountDownLatch 被初始化为10,代表有10个任务需要完成。每个线程在执行完自己的逻辑后调用 countDown(),使计数器递减。主线程调用 await() 后进入阻塞状态,直到所有子线程都调用了 countDown(),计数器变为0,await() 返回,主线程继续执行后续逻辑。
值得注意的是,CountDownLatch 的计数器只能使用一次。一旦计数器归零,后续再调用 await() 将立即返回,而 countDown() 也不会再产生任何效果。这与 CyclicBarrier 不同,后者可以重复使用。因此,如果你需要多次等待一组线程完成,应该考虑使用 CyclicBarrier 或者重新创建一个新的 CountDownLatch 实例。
此外,await() 方法有两种形式:一种是无参的,会一直阻塞直到计数器归零;另一种是带超时参数的 await(long timeout, TimeUnit unit),它允许设置最长等待时间。如果在指定时间内计数器仍未归零,方法会返回 false,这样可以避免无限期阻塞,提高程序的健壮性。
在实际项目中,CountDownLatch 常用于以下几种典型场景:
- 主线程等待多个异步任务完成后再进行汇总处理;
- 多个服务组件初始化完成后,主服务才开始对外提供服务;
- 并行计算中,等待所有子任务完成后再合并结果;
- 测试多线程程序时,确保所有线程执行完毕再进行断言。
总之,CountDownLatch 是Java并发包中一个简单但极其实用的工具。它通过“倒计数”的方式实现了线程间的协调与同步,使得复杂的多线程控制变得清晰可控。掌握它的使用方法,不仅能提升代码的可读性和可靠性,也能帮助我们更好地理解Java并发编程的核心思想。
