讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 建議130:使用CountDownLatch協調子線程 >

建議130:使用CountDownLatch協調子線程

思考這樣一個案例:百米賽跑,多個參加賽跑的人員在聽到發令槍響後,開始跑步,到達終點後結束計時,然後統計平均成績。這裡有兩點需要考慮:一是發令槍響,這是所有跑步者(線程)接收到的出發信號,此處涉及裁判(主線程)如何通知跑步者(子線程)的問題;二是如何獲知所有的跑步者完成了賽跑,也就是主線程如何知道子線程已經全部完成,這有很多種實現方式,此處我們使用CountDownLatch工具類來實現,代碼如下:


static class Runner implements Callable<Integer>{

//開始信號

private CountDownLatch begin;

//結束信號

private CountDownLatch end;

public Runner(CountDownLatch_begin, CountDownLatch_end){

begin=_begin;

end=_end;

}

@Override

public Integer call()throws Exception{

//跑步的成績

int score=new Random().nextInt(25);

//等待發令槍響起

begin.await();

//跑步中……

TimeUnit.MILLISECONDS.sleep(score);

//跑步者已經跑完全程

end.countDown();

return score;

}

}

public static void main(Stringargs)throws Exception{

//參加賽跑人數

int num=10;

//發令槍只響一次

CountDownLatch begin=new CountDownLatch(1);

//參與跑步有多個

CountDownLatch end=new CountDownLatch(num);

//每個跑步者一個跑道

ExecutorService es=Executors.newFixedThreadPool(num);

//記錄比賽成績

List<Future<Integer>>futures=new ArrayList<Future<Integer>>();

//跑步者就位,所有線程處於等待狀態

for(int i=0;i<num;i++){

futures.add(es.submit(new Runner(begin, end)));

}

//發令槍響,跑步者開始跑步

begin.countDown();

//等待所有跑步者跑完全程

end.await();

int count=0;

//統計總分

for(Future<Integer>f:futures){

count+=f.get();

}

System.out.println("平均分數為:"+count/num);

}


CountDownLatch類是一個倒數的同步計數器,在程序中啟動了兩個計數器:一個是開始計數器begin,表示的是發令槍;另外是結束計數器,一共有10個,表示的是每個線程的執行情況,也就是跑步者是否跑完比賽。程序執行邏輯如下:

1)10個線程都開始運行,執行到begin.await後線程阻塞,等待begin的計數變為0。

2)主線程調用begin的countDown方法,使begin的計數器為0。

3)10個線程繼續運行。

4)主線程繼續運行下一個語句,end的計數器不為0,主線程等待。

5)每個線程運行結束時把end的計數器減1,標誌著本線程運行完畢。

6)10個線程全部結束,end計數器為0。

7)主線程繼續執行,打印出成績平均值。

CountDownLatch的作用是控制一個計數器,每個線程在運行完畢後會執行countDown,表示自己運行結束,這對於多個子任務的計算特別有效,比如一個異步任務需要拆分成10個子任務執行,主任務必須要知道子任務是否完成,所有子任務完成後才能進行合併計算,從而保證了一個主任務的邏輯正確性。這和我們的實際工作非常類似,比如領導安排了一個大任務給我,我一個人不可能完成,於是我把該任務分解給10個人做,在10個人全部完成後,我把這10個結果組合起來返回給領導——這就是CountDownLatch的作用。