原創(chuàng)文章&經(jīng)驗總結(jié)&從校招到A廠一路陽光一路滄桑
詳情請戳www.codercc.com
1. 倒計時器CountDownLatch
在多線程協(xié)作完成業(yè)務功能時,有時候需要等待其他多個線程完成任務之后,主線程才能繼續(xù)往下執(zhí)行業(yè)務功能,在這種的業(yè)務場景下,通??梢允褂肨hread類的join方法,讓主線程等待被join的線程執(zhí)行完之后,主線程才能繼續(xù)往下執(zhí)行。當然,使用線程間消息通信機制也可以完成。其實,java并發(fā)工具類中為我們提供了類似“倒計時”這樣的工具類,可以十分方便的完成所說的這種業(yè)務場景。
為了能夠理解CountDownLatch,舉一個很通俗的例子,運動員進行跑步比賽時,假設(shè)有6個運動員參與比賽,裁判員在終點會為這6個運動員分別計時,可以想象沒當一個運動員到達終點的時候,對于裁判員來說就少了一個計時任務。直到所有運動員都到達終點了,裁判員的任務也才完成。這6個運動員可以類比成6個線程,當線程調(diào)用CountDownLatch.countDown方法時就會對計數(shù)器的值減一,直到計數(shù)器的值為0的時候,裁判員(調(diào)用await方法的線程)才能繼續(xù)往下執(zhí)行。
下面來看些CountDownLatch的一些重要方法。
先從CountDownLatch的構(gòu)造方法看起:
public CountDownLatch(int count)
構(gòu)造方法會傳入一個整型數(shù)N,之后調(diào)用CountDownLatch的countDown方法會對N減一,知道N減到0的時候,當前調(diào)用await方法的線程繼續(xù)執(zhí)行。
CountDownLatch的方法不是很多,將它們一個個列舉出來:
- await() throws InterruptedException:調(diào)用該方法的線程等到構(gòu)造方法傳入的N減到0的時候,才能繼續(xù)往下執(zhí)行;
- await(long timeout, TimeUnit unit):與上面的await方法功能一致,只不過這里有了時間限制,調(diào)用該方法的線程等到指定的timeout時間后,不管N是否減至為0,都會繼續(xù)往下執(zhí)行;
- countDown():使CountDownLatch初始值N減1;
- long getCount():獲取當前CountDownLatch維護的值;
下面用一個具體的例子來說明CountDownLatch的具體用法:
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("裁判員發(fā)號施令啦!??!");
startSignal.countDown();
endSignal.await();
System.out.println("所有運動員到達終點,比賽結(jié)束!");
executorService.shutdown();
}
}
輸出結(jié)果:
pool-1-thread-2 運動員等待裁判員響哨?。?!
pool-1-thread-3 運動員等待裁判員響哨!??!
pool-1-thread-1 運動員等待裁判員響哨!??!
pool-1-thread-4 運動員等待裁判員響哨!!!
pool-1-thread-5 運動員等待裁判員響哨!??!
pool-1-thread-6 運動員等待裁判員響哨!??!
裁判員發(fā)號施令啦?。?!
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 到達終點
所有運動員到達終點,比賽結(jié)束!
該示例代碼中設(shè)置了兩個CountDownLatch,第一個endSignal用于控制讓main線程(裁判員)必須等到其他線程(運動員)讓CountDownLatch維護的數(shù)值N減到0為止。另一個startSignal用于讓main線程對其他線程進行“發(fā)號施令”,startSignal引用的CountDownLatch初始值為1,而其他線程執(zhí)行的run方法中都會先通過 startSignal.await()讓這些線程都被阻塞,直到main線程通過調(diào)用startSignal.countDown();,將值N減1,CountDownLatch維護的數(shù)值N為0后,其他線程才能往下執(zhí)行,并且,每個線程執(zhí)行的run方法中都會通過endSignal.countDown();對endSignal維護的數(shù)值進行減一,由于往線程池提交了6個任務,會被減6次,所以endSignal維護的值最終會變?yōu)?,因此main線程在latch.await();阻塞結(jié)束,才能繼續(xù)往下執(zhí)行。
另外,需要注意的是,當調(diào)用CountDownLatch的countDown方法時,當前線程是不會被阻塞,會繼續(xù)往下執(zhí)行,比如在該例中會繼續(xù)輸出pool-1-thread-4 到達終點。
2. 循環(huán)柵欄:CyclicBarrier
CyclicBarrier也是一種多線程并發(fā)控制的實用工具,和CountDownLatch一樣具有等待計數(shù)的功能,但是相比于CountDownLatch功能更加強大。
為了理解CyclicBarrier,這里舉一個通俗的例子。開運動會時,會有跑步這一項運動,我們來模擬下運動員入場時的情況,假設(shè)有6條跑道,在比賽開始時,就需要6個運動員在比賽開始的時候都站在起點了,裁判員吹哨后才能開始跑步。跑道起點就相當于“barrier”,是臨界點,而這6個運動員就類比成線程的話,就是這6個線程都必須到達指定點了,意味著湊齊了一波,然后才能繼續(xù)執(zhí)行,否則每個線程都得阻塞等待,直至湊齊一波即可。cyclic是循環(huán)的意思,也就是說CyclicBarrier當多個線程湊齊了一波之后,仍然有效,可以繼續(xù)湊齊下一波。CyclicBarrier的執(zhí)行示意圖如下:

當多個線程都達到了指定點后,才能繼續(xù)往下繼續(xù)執(zhí)行。這就有點像報數(shù)的感覺,假設(shè)6個線程就相當于6個運動員,到賽道起點時會報數(shù)進行統(tǒng)計,如果剛好是6的話,這一波就湊齊了,才能往下執(zhí)行。CyclicBarrier在使用一次后,下面依然有效,可以繼續(xù)當做計數(shù)器使用,這是與CountDownLatch的區(qū)別之一。這里的6個線程,也就是計數(shù)器的初始值6,是通過CyclicBarrier的構(gòu)造方法傳入的。
下面來看下CyclicBarrier的主要方法:
//等到所有的線程都到達指定的臨界點
await() throws InterruptedException, BrokenBarrierException
//與上面的await方法功能基本一致,只不過這里有超時限制,阻塞等待直至到達超時時間為止
await(long timeout, TimeUnit unit) throws InterruptedException,
BrokenBarrierException, TimeoutException
//獲取當前有多少個線程阻塞等待在臨界點上
int getNumberWaiting()
//用于查詢阻塞等待的線程是否被中斷
boolean isBroken()
//將屏障重置為初始狀態(tài)。如果當前有線程正在臨界點等待的話,將拋出BrokenBarrierException。
void reset()
另外需要注意的是,CyclicBarrier提供了這樣的構(gòu)造方法:
public CyclicBarrier(int parties, Runnable barrierAction)
可以用來,當指定的線程都到達了指定的臨界點的時,接下來執(zhí)行的操作可以由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() + " 運動員出發(fā)");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
});
}
}
}
輸出結(jié)果:
運動員準備進場,全場歡呼............
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 運動員出發(fā)
pool-1-thread-1 運動員出發(fā)
pool-1-thread-5 運動員出發(fā)
pool-1-thread-4 運動員出發(fā)
pool-1-thread-3 運動員出發(fā)
pool-1-thread-2 運動員出發(fā)
從輸出結(jié)果可以看出,當6個運動員(線程)都到達了指定的臨界點(barrier)時候,才能繼續(xù)往下執(zhí)行,否則,則會阻塞等待在調(diào)用await()處
3. CountDownLatch與CyclicBarrier的比較
CountDownLatch與CyclicBarrier都是用于控制并發(fā)的工具類,都可以理解成維護的就是一個計數(shù)器,但是這兩者還是各有不同側(cè)重點的:
- CountDownLatch一般用于某個線程A等待若干個其他線程執(zhí)行完任務之后,它才執(zhí)行;而CyclicBarrier一般用于一組線程互相等待至某個狀態(tài),然后這一組線程再同時執(zhí)行;CountDownLatch強調(diào)一個線程等多個線程完成某件事情。CyclicBarrier是多個線程互等,等大家都完成,再攜手共進。
- 調(diào)用CountDownLatch的countDown方法后,當前線程并不會阻塞,會繼續(xù)往下執(zhí)行;而調(diào)用CyclicBarrier的await方法,會阻塞當前線程,直到CyclicBarrier指定的線程全部都到達了指定點的時候,才能繼續(xù)往下執(zhí)行;
- CountDownLatch方法比較少,操作比較簡單,而CyclicBarrier提供的方法更多,比如能夠通過getNumberWaiting(),isBroken()這些方法獲取當前多個線程的狀態(tài),并且CyclicBarrier的構(gòu)造方法可以傳入barrierAction,指定當所有線程都到達時執(zhí)行的業(yè)務功能;
- CountDownLatch是不能復用的,而CyclicLatch是可以復用的。