在之前的文章中已经为大家介绍了java并发编程的工具:BlockingQueue接口、ArrayBlockingQueue、DelayQueue、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue、BlockingDeque接口、ConcurrentHashMap,本文为系列文章第九篇。
CountDownLatch是一种线程同步辅助工具,它允许一个或多个线程等待其他线程正在执行的一组操作完成。CountDownLatch的概念在java并发编程中非常常见,面试也会经常被问到,所以一定要好好理解掌握。在这篇文章中,我将介绍以下几点
- CountDownLatch是什么?
- CountDownLatch 如何工作
- CountDownLatch 代码例子
CountDownLatch是什么?
CountDownLatch与其他并发编程工具类,如CyclicBarrier、Semaphore、ConcurrentHashMap和BlockingQueue等在java.util.concurrent包中与JDK 1.5一起被引入。CountDownLatch能让一个java线程等待其他线程完成任务,比如Application的主线程等待,直到其他负责启动框架服务的服务线程完成所有服务的启动。
CountDownLatch用线程数来初始化一个计数器,每当一个线程完成执行时,这个计数器就会递减。当计数为零时,表示所有线程都已完成执行,处于等待状态的主线程可以继续执行。
下面我们使用伪代码的方式描述CountDownLatch 的作用
- 主线程启动,并为N个线程(假设n=3)初始化CountDownLatch(n)
- 启动n个线程
- 主线程阻塞等待
- 线程1执行完成,CountDownLatch -1 = 2,主线程继续阻塞
- 线程3执行完成,CountDownLatch -1 = 1,主线程继续阻塞
- 线程4执行完成,CountDownLatch -1 = 0,主线程恢复执行
CountDownLatch 如何工作
CountDownLatch.java类里面定义了一个构造函数。count实质上是线程数,这个值只能设置一次,CountDownLatch没有提供方法来重置这个数。
代码语言:javascript复制CountDownLatch.public CountDownLatch(int count) {...}
使用CountDownLatch的主线程要去等待其他线程执行完成,所以这个主线程必须在启动其他线程后立即调用 CountDownLatch.await()
方法,该方法阻塞主线程处于等待状态,直到其他线程执行完毕,才会停止阻塞。
其他N个线程必须有CountDownLatch对象的引用,因为它们需要通知CountDownLatch对象它们已经完成任务。这个通知是由方法CountDownLatch.countDown()
来完成的,每调用一次该方法,就会将构造函数中设置的初始计数count减少1,所以当所有N个线程都调用了这个方法后count计数达到0,主线程就可以不受await()方法阻塞恢复执行了。
所以CountDownLatch特别适合于那些需要等待N个线程完成后再开始执行的场景。例如一个应用程序的启动类,在处理用户请求之前,要确保所有N个外部系统都是处于运行状态的。
CountDownLatch 代码例子
假设我们的应用程序主线程启动之前,要检查另外4个程序是否准备就绪,只有其他的4个程序准备就绪,我们的主程序才能继续执行。就可以使用下面的代码来操作:
代码语言:javascript复制import java.util.concurrent.CountDownLatch;
public class Tester {
public static void main(String args[]) {
//设置计数器 counter = 4 ,等于线程数
CountDownLatch countDownLatch = new CountDownLatch(4);
Thread app1 = new Thread(new Application("App1", countDownLatch));
Thread app2 = new Thread(new Application("App2", countDownLatch));
Thread app3 = new Thread(new Application("App3", countDownLatch));
Thread app4 = new Thread(new Application("App4", countDownLatch));
// 启动多线程去检查其他四个程序的可用状态
app1.start();
app2.start();
app3.start();
app4.start();
try {
//主线程调用await进行等待,等待上述四个线程正常完成
countDownLatch.await();
//上述四个线程检查的应用程序启动正常之后, 打印如下信息
System.out.println("All applications are up and running.");
} catch(InterruptedException e) {
System.out.println(e.getMessage());
}
}
}
子线程程序,每一个线程都持有countDownLatch对象,线程正常执行完成之时,使用countDownLatch.countDown()
方法将countDownLatch对象的计数器减1。
class Application implements Runnable {
private String name; //应用程序名称
private CountDownLatch countDownLatch;
public Application(String name, CountDownLatch countDownLatch) {
this.name = name;
this.countDownLatch = countDownLatch;
}
public void run() {
try {
System.out.println(name " started. ");
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
System.out.println( name " is Up and running.");
//将countDownLatch计数器的值减1
countDownLatch.countDown();
}
}
上述程序的打印输出结果是,可以结合输出结果去理解上文中讲述的CountDownLatch 工作原理:
代码语言:javascript复制App2 started.
App3 started.
App1 started.
App4 started.
App1 is Up and running.
App3 is Up and running.
App4 is Up and running.
App2 is Up and running.
All applications are up and running.