
在之前的文章中已經為大家介紹了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沒有提供方法**來重置這個數**。
~~~
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個程序準備就緒,我們的主程序才能繼續執行。就可以使用下面的代碼來操作:
~~~
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 工作原理:
~~~
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.
~~~
- 線程
- 1.進程和線程-鎖與信號量
- 2.Thread類線程狀態轉換
- 2.并發與并行-同步與異步
- 4.線程池
- 5.對象級別與類級別的同步鎖
- 6.創建線程的四種方式
- 7.臨界區-阻塞-活鎖-死鎖
- 2.JMM多線程模型
- JUC
- BlockingQueue
- ArrayBlockingQueue
- DelayQueue
- LinkedBlockingQueue
- PriorityBlockingQueue
- SynchronousQueue
- BlockingDeque
- ConcurrentHashMap
- CountDownLatch
- CyclicBarrier
- Exchanger
- AtomicInteger
- Lock
- Condition
- ReentrantLock讀寫鎖
- StampedLock
- Semaphore