在多線程程序設計中,常常會遇到一個線程等待一個或多個線程的場景,遇到這樣的場景應該如何解決?java
若是是一個線程等待一個線程,則能夠經過await()和notify()來實現;編程
若是是一個線程等待多個線程,則就可使用CountDownLatch和CyclicBarrier來實現比較好的控制。多線程
下面來詳細描述下CountDownLatch的應用場景:併發
例如:百米賽跑:8名運動員同時起跑,因爲速度的快慢,確定有會出現先到終點和晚到終點的狀況,而終點有個統計成績的儀器,當全部選手到達終點時,它會統計全部人的成績並進行排序,而後把結果發送到彙報成績的系統。ide
其實這就是一個CountDownLatch的應用場景:一個線程或多個線程等待其餘線程運行達到某一目標後進行本身的下一步工做,而被等待的「其餘線程」達到這個目標後繼續本身下面的任務。this
這個場景中:spa
1. 被等待的「其餘線程」------>8名運動員線程
2. 等待「其餘線程」的這個線程------>終點統計成績的儀器設計
那麼,如何來經過CountDownLatch來實現上述場景的線程控制和調度呢?code
jdk中CountDownLatch類有一個經常使用的構造方法:CountDownLatch(int count);
兩個經常使用的方法:await()和countdown()
其 中count是一個計數器中的初始化數字,好比初始化的數字是2,當一個線程裏調用了countdown(),則這個計數器就減一,當線程調用了 await(),則這個線程就等待這個計數器變爲0,當這個計數器變爲0時,這個線程繼續本身下面的工做。下面是上述CountDownLatch場景的 實現:
Work類(運動員):
import java.util.concurrent.CountDownLatch; public class Work implements Runnable { private int id; private CountDownLatch beginSignal; private CountDownLatch endSignal; public Work(int id, CountDownLatch begin, CountDownLatch end) { this.id = id; this.beginSignal = begin; this.endSignal = end; } @Override public void run() { try { System.out.println("準備完畢.."+id); beginSignal.await(); System.out.println("起跑..."+id); System.out.println("work" + id + "到達終點"); endSignal.countDown(); System.out.println("work" + id + "繼續幹其餘事情"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static void main(String[] args) { CountDownLatch begSignal = new CountDownLatch(1); CountDownLatch endSignal = new CountDownLatch(8); for (int i = 0; i < 8; i++) { new Thread(new Work(i, begSignal, endSignal)).start(); } try { Thread.currentThread().sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("-------------------------------"); try { begSignal.countDown(); //統一塊兒跑 endSignal.await(); //等待運動員到達終點 System.out.println("結果發送到彙報成績的系統"); } catch (InterruptedException e) { e.printStackTrace(); } } }
輸出結果:
準備完畢..4
準備完畢..5
準備完畢..1
準備完畢..2
準備完畢..6
準備完畢..0
準備完畢..3
準備完畢..7
-------------------------------
起跑...4
起跑...2
起跑...6
work6到達終點
起跑...1
work1到達終點
起跑...5
work5到達終點
work1繼續幹其餘事情
work6繼續幹其餘事情
起跑...7
work7到達終點
work7繼續幹其餘事情
起跑...3
work3到達終點
work3繼續幹其餘事情
起跑...0
work2到達終點
work2繼續幹其餘事情
work4到達終點
work4繼續幹其餘事情
work0到達終點
work0繼續幹其餘事情
work5繼續幹其餘事情
結果發送到彙報成績的系統
下面詳細描述下CyclicBarrier的應用場景:
有四個遊戲玩家玩遊戲,遊戲有三個關卡,每一個關卡必需要全部玩家都到達後才能容許通關。
其 實這個場景裏的玩家中若是有玩家A先到了關卡1,他必須等待其餘全部玩家都到達關卡1時才能經過,也就是說線程之間須要互相等待,這和 CountDownLatch的應用場景有區別,CountDownLatch裏的線程是到了運行的目標後繼續幹本身的其餘事情,而這裏的線程須要等待其 他線程後才能繼續完成下面的工做。
jdk中CyclicBarrier類有兩個經常使用的構造方法:
1. CyclicBarrier(int parties)
這裏的parties也是一個計數器,例如,初始化時parties裏的計數是3,因而擁有該CyclicBarrier對象的線程當parties的計數爲3時就喚醒,注:這裏parties裏的計數在運行時當調用CyclicBarrier:await()時,計數就加1,一直加到初始的值
2. CyclicBarrier(int parties, Runnable barrierAction)
這裏的parties與上一個構造方法的解釋是同樣的,這裏須要解釋的是第二個入參(Runnable barrierAction),這個參數是一個實現Runnable接口的類的對象,也就是說當parties加到初始值時就出發barrierAction的內容。
下面來實現上述的應用場景:
Player類(玩家類)
java.util.concurrent.BrokenBarrierException java.util.concurrent.CyclicBarrier public class Player implements Runnable{ private CyclicBarrier cyclicBarrier; private int id; public Player(int id, CyclicBarrier cyclicBarrier) { this.cyclicBarrier = cyclicBarrier; this.id = id; } @Override public void run() { try { System.out.println("玩家" + id + "正在玩第一關..."); cyclicBarrier.await(); System.out.println("玩家" + id + "進入第二關..."); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } public static void main(String[] args) { CyclicBarrier cyclicBarrier = new CyclicBarrier(4, new Runnable() { @Override public void run() { System.out.println("全部玩家進入第二關!"); } }); for (int i = 0; i < 4; i++) { new Thread(new Player(i, cyclicBarrier)).start(); } } }
輸出結果:
玩家1正在玩第一關...
玩家2正在玩第一關...
玩家3正在玩第一關...
玩家0正在玩第一關...
全部玩家進入第二關!
玩家0進入第二關...
玩家1進入第二關...
玩家2進入第二關...
玩家3進入第二關...