在多線程協做完成業務功能時,有時候須要等待其餘多個線程完成任務以後,主線程才能繼續往下執行業務功能,在這種的業務場景下,一般可使用Thread類的join方法,讓主線程等待被join的線程執行完以後,主線程才能繼續往下執行。固然,使用線程間消息通訊機制也能夠完成。其實,java併發工具類中爲咱們提供了相似「倒計時」這樣的工具類,能夠十分方便的完成所說的這種業務場景。java
爲了可以理解CountDownLatch,舉一個很通俗的例子,運動員進行跑步比賽時,假設有6個運動員參與比賽,裁判員在終點會爲這6個運動員分別計時,能夠想象沒當一個運動員到達終點的時候,對於裁判員來講就少了一個計時任務。直到全部運動員都到達終點了,裁判員的任務也才完成。這6個運動員能夠類比成6個線程,當線程調用CountDownLatch.countDown方法時就會對計數器的值減一,直到計數器的值爲0的時候,裁判員(調用await方法的線程)才能繼續往下執行。多線程
下面來看些CountDownLatch的一些重要方法。併發
先從CountDownLatch的構造方法看起:工具
public CountDownLatch(int count)
複製代碼
構造方法會傳入一個整型數N,以後調用CountDownLatch的countDown
方法會對N減一,知道N減到0的時候,當前調用await
方法的線程繼續執行。spa
CountDownLatch的方法不是不少,將它們一個個列舉出來:線程
下面用一個具體的例子來講明CountDownLatch的具體用法:code
public class CountDownLatchDemo {
private static CountDownLatch startSignal = new CountDownLatch(1);
//用來表示裁判員須要維護的是6個運動員
private static CountDownLatch endSignal = new CountDownLatch(6);
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(6);
for (int i = 0; i < 6; i++) {
executorService.execute(() -> {
try {
System.out.println(Thread.currentThread().getName() + " 運動員等待裁判員響哨!!!");
startSignal.await();
System.out.println(Thread.currentThread().getName() + "正在全力衝刺");
endSignal.countDown();
System.out.println(Thread.currentThread().getName() + " 到達終點");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
System.out.println("裁判員發號施令啦!!!");
startSignal.countDown();
endSignal.await();
System.out.println("全部運動員到達終點,比賽結束!");
executorService.shutdown();
}
}
輸出結果:
pool-1-thread-2 運動員等待裁判員響哨!!!
pool-1-thread-3 運動員等待裁判員響哨!!!
pool-1-thread-1 運動員等待裁判員響哨!!!
pool-1-thread-4 運動員等待裁判員響哨!!!
pool-1-thread-5 運動員等待裁判員響哨!!!
pool-1-thread-6 運動員等待裁判員響哨!!!
裁判員發號施令啦!!!
pool-1-thread-2正在全力衝刺
pool-1-thread-2 到達終點
pool-1-thread-3正在全力衝刺
pool-1-thread-3 到達終點
pool-1-thread-1正在全力衝刺
pool-1-thread-1 到達終點
pool-1-thread-4正在全力衝刺
pool-1-thread-4 到達終點
pool-1-thread-5正在全力衝刺
pool-1-thread-5 到達終點
pool-1-thread-6正在全力衝刺
pool-1-thread-6 到達終點
全部運動員到達終點,比賽結束!
複製代碼
該示例代碼中設置了兩個CountDownLatch,第一個endSignal
用於控制讓main線程(裁判員)必須等到其餘線程(運動員)讓CountDownLatch維護的數值N減到0爲止。另外一個startSignal
用於讓main線程對其餘線程進行「發號施令」,startSignal引用的CountDownLatch初始值爲1,而其餘線程執行的run方法中都會先經過 startSignal.await()
讓這些線程都被阻塞,直到main線程經過調用startSignal.countDown();
,將值N減1,CountDownLatch維護的數值N爲0後,其餘線程才能往下執行,而且,每一個線程執行的run方法中都會經過endSignal.countDown();
對endSignal
維護的數值進行減一,因爲往線程池提交了6個任務,會被減6次,因此endSignal
維護的值最終會變爲0,所以main線程在latch.await();
阻塞結束,才能繼續往下執行。cdn
另外,須要注意的是,當調用CountDownLatch的countDown方法時,當前線程是不會被阻塞,會繼續往下執行,好比在該例中會繼續輸出pool-1-thread-4 到達終點
。blog
CyclicBarrier也是一種多線程併發控制的實用工具,和CountDownLatch同樣具備等待計數的功能,可是相比於CountDownLatch功能更增強大。get
爲了理解CyclicBarrier,這裏舉一個通俗的例子。開運動會時,會有跑步這一項運動,咱們來模擬下運動員入場時的狀況,假設有6條跑道,在比賽開始時,就須要6個運動員在比賽開始的時候都站在起點了,裁判員吹哨後才能開始跑步。跑道起點就至關於「barrier」,是臨界點,而這6個運動員就類比成線程的話,就是這6個線程都必須到達指定點了,意味着湊齊了一波,而後才能繼續執行,不然每一個線程都得阻塞等待,直至湊齊一波便可。cyclic是循環的意思,也就是說CyclicBarrier當多個線程湊齊了一波以後,仍然有效,能夠繼續湊齊下一波。CyclicBarrier的執行示意圖以下:
當多個線程都達到了指定點後,才能繼續往下繼續執行。這就有點像報數的感受,假設6個線程就至關於6個運動員,到賽道起點時會報數進行統計,若是恰好是6的話,這一波就湊齊了,才能往下執行。**CyclicBarrier在使用一次後,下面依然有效,能夠繼續當作計數器使用,這是與CountDownLatch的區別之一。**這裏的6個線程,也就是計數器的初始值6,是經過CyclicBarrier的構造方法傳入的。
下面來看下CyclicBarrier的主要方法:
//等到全部的線程都到達指定的臨界點
await() throws InterruptedException, BrokenBarrierException
//與上面的await方法功能基本一致,只不過這裏有超時限制,阻塞等待直至到達超時時間爲止
await(long timeout, TimeUnit unit) throws InterruptedException,
BrokenBarrierException, TimeoutException
//獲取當前有多少個線程阻塞等待在臨界點上
int getNumberWaiting()
//用於查詢阻塞等待的線程是否被中斷
boolean isBroken()
//將屏障重置爲初始狀態。若是當前有線程正在臨界點等待的話,將拋出BrokenBarrierException。
void reset()
複製代碼
另外須要注意的是,CyclicBarrier提供了這樣的構造方法:
public CyclicBarrier(int parties, Runnable barrierAction)
複製代碼
能夠用來,當指定的線程都到達了指定的臨界點的時,接下來執行的操做能夠由barrierAction傳入便可。
一個例子
下面用一個簡單的例子,來看下CyclicBarrier的用法,咱們來模擬下上面的運動員的例子。
public class CyclicBarrierDemo {
//指定必須有6個運動員到達才行
private static CyclicBarrier barrier = new CyclicBarrier(6, () -> {
System.out.println("全部運動員入場,裁判員一聲令下!!!!!");
});
public static void main(String[] args) {
System.out.println("運動員準備進場,全場歡呼............");
ExecutorService service = Executors.newFixedThreadPool(6);
for (int i = 0; i < 6; i++) {
service.execute(() -> {
try {
System.out.println(Thread.currentThread().getName() + " 運動員,進場");
barrier.await();
System.out.println(Thread.currentThread().getName() + " 運動員出發");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
});
}
}
}
輸出結果:
運動員準備進場,全場歡呼............
pool-1-thread-2 運動員,進場
pool-1-thread-1 運動員,進場
pool-1-thread-3 運動員,進場
pool-1-thread-4 運動員,進場
pool-1-thread-5 運動員,進場
pool-1-thread-6 運動員,進場
全部運動員入場,裁判員一聲令下!!!!!
pool-1-thread-6 運動員出發
pool-1-thread-1 運動員出發
pool-1-thread-5 運動員出發
pool-1-thread-4 運動員出發
pool-1-thread-3 運動員出發
pool-1-thread-2 運動員出發
複製代碼
從輸出結果能夠看出,當6個運動員(線程)都到達了指定的臨界點(barrier)時候,才能繼續往下執行,不然,則會阻塞等待在調用await()
處
CountDownLatch與CyclicBarrier都是用於控制併發的工具類,均可以理解成維護的就是一個計數器,可是這二者仍是各有不一樣側重點的: