CountDownLatch类的作用
CountDownLatch存在于java.util.cucurrent包下,是一个多线程控制工具类,它允许一个线程一直等待,直到其他线程执行完后再执行该线程。
例如:应用程序的主线程希望在负责启动框架服务的线程已经启动所有框架服务之后执行。
CountDownLatch原理
CountDownLatch是通过一个计数器来实现的,计数器的初始化值为:线程的数量【需要执行完的线程数量】。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。
CountDownLatch的常用API
- 构造器
//参数count为计数值
public CountDownLatch(int count) { };
- 三个常用的普通方法
//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public void await() throws InterruptedException { };
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };
//将count值减1
public void countDown() { };
- 构造器参数count是需要等待执行完成的线程数量,只能被设置一次,且CountDownLatch没有提供任何机制去重新设置计数器count。
- 与CountDownLatch的第一次交互是主线程等待其他线程。主线程必须在启动其他线程后立即调用CountDownLatch.await()方法。这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。
- 当count的值等于0,主线程就可以通过await()方法,恢复执行自己的任务
CountDownLatch的举例
public class Demo {
public static void main(String[] args) throws InterruptedException {
System.out.println("main线程开始");
final CountDownLatch countDownLatch = new CountDownLatch(2);
IntStream.rangeClosed(1, 2).forEach(e -> new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "正在工作");
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
countDownLatch.countDown();
}, String.valueOf(e)).start()
);
countDownLatch.await();
System.out.println("main线程结束");
}
}
实际应用场景
- 测试最大的并行线程数:同时启动多个线程,测试可以并行的最大线程数。例如,我们想测试一个单例类。如果我们创建一个初始计数器为1的CountDownLatch,并让其他所有线程都在这个锁上等待,只需要调用一次countDown()方法就可以让其他所有等待的线程同时恢复执行。
注意:countDownLatch.await()后执行业务逻辑时,countDownLatch.countDown()放行后需要一定的等待时间,等待业务逻辑处理完成后才能继续执行,否则无法正确获取其他线程运行结果(还没执行完, countDownLatch.countDown()所在线程已经结束了)
public class Demo {
private static Integer num = 0;
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
final CountDownLatch countDownLatch = new CountDownLatch(1);
for (int i = 0; i < 10; i++) {
executorService.submit(() -> {
try {
//让单个线程等待
countDownLatch.await();
//在await方法后待执行的事情如果是多个线程同时
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getOrderNo());
});
}
//一次性放行10个阻塞线程
countDownLatch.countDown();
executorService.shutdown();
}
public static String getOrderNo() {
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");
return sf.format(new Date()) + num++;
}
}
- 开始执行前等待N个线程完成各自任务:例如应用程序启动类要确保在处理用户请求前,所有N个外部系统都已经启动和运行了。
- 死锁检测:用N个线程去访问共享资源,在每个测试阶段线程数量不同,并尝试产生死锁。