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