
CyclicBarrier是java.util.concurrent包下面的一個工具類,字面意思是可循環使用(Cyclic)的屏障(Barrier),通過它可以實作讓一組線程到達一個屏障(也可以叫同步點)時被阻塞,直到最後一個線程到達屏障時,所有被屏障攔截的線程才會繼續執行。
這篇文章将介紹CyclicBarrier這個同步工具類的以下幾點
- 通過案例分析
- 兩種不同構造函數測試
- CyclicBarrier和CountDownLatch的差別
- await方法及源碼分析。
需求
繼上一篇
CountDownLatch
模拟遊戲加載後,現在使用者點選開始按鈕後,需要比對包括自己在内的五個玩家才能開始遊戲,比對玩家成功後進入到選擇角色階段。當5位玩家角色都選擇完畢後,開始進入遊戲。進入遊戲時需要加載相關的資料,待全部玩家都加載完畢後正式開始遊戲。
解決方案
從需求中可以知道,想要開始遊戲需要經過三個階段,分别是
- 比對玩家
- 選擇角色
- 加載資料
在這三個階段中,都需要互相等待對方完成才能繼續進入下個階段。
這時可以采用
CyclicBarrier
來作為各個階段的節點,等待其他玩家到達,在進入下個階段。
定義繼承Runnable的類
這裡名稱就叫做
StartGame
,包含兩個屬性
private String player;
private CyclicBarrier barrier;
通過構造函數初始化兩個屬性
public StartGame(String player, CyclicBarrier barrier) {
this.player = player;
this.barrier = barrier;
}
run方法如下
public void run() {
try {
System.out.println(this.getPlayer()+" 開始比對玩家...");
findOtherPlayer();
barrier.await();
System.out.println(this.getPlayer()+" 進行選擇角色...");
choiceRole();
System.out.println(this.getPlayer()+" 角色選擇完畢等待其他玩家...");
barrier.await();
System.out.println(this.getPlayer()+" 開始遊戲,進行遊戲加載...");
loading();
System.out.println(this.getPlayer()+" 遊戲加載完畢等待其他玩家加載完成...");
barrier.await();
start();
} catch (Exception e){
e.printStackTrace();
}
}
其他的方法findOtherPlayer()、choiceRole()等待使用
Thread.sleep()
來模拟花費時間
編寫測試代碼
CyclicBarrier有兩個構造函數,如下
public CyclicBarrier(int parties) {}
public CyclicBarrier(int parties, Runnable barrierAction) {}
先來看看一個參數的構造函數
CyclicBarrier(int parties)
public static void main(String[] args) throws IOException {
CyclicBarrier barrier = new CyclicBarrier(5);
Thread player1 = new Thread(new StartGame("1",barrier));
Thread player2 = new Thread(new StartGame("2",barrier));
Thread player3 = new Thread(new StartGame("3",barrier));
Thread player4 = new Thread(new StartGame("4",barrier));
Thread player5 = new Thread(new StartGame("5",barrier));
player1.start();
player2.start();
player3.start();
player4.start();
player5.start();
System.in.read();
}
測試結果如下
CyclicBarrier(int parties, Runnable barrierAction)
CyclicBarrier barrier = new CyclicBarrier(5);
替換為
CyclicBarrier barrier = new CyclicBarrier(5, () -> {
try {
System.out.println("階段完成,等待2秒...");
Thread.sleep(2000);
System.out.println("進入下個階段...");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
再來看看效果
可以看到在到達某個節點時,會執行執行個體化CyclicBarrier時傳入的Runnable對象。而且每一次到達都會執行一次。
CountDownLatch | CyclicBarrier |
---|---|
計數為0時,無法重置 | 計數達到0時,計數置為傳入的值重新開始 |
調用countDown()方法計數減一,調用await()方法隻進行阻塞,對計數沒任何影響 | 調用await()方法計數減一,若減一後的值不等于0,則線程阻塞 |
不可重複使用 | 可重複使用 |
await方法
public int await(){}
public int await(long timeout, TimeUnit unit){}
無參的await方法這裡就不做介紹了,主要介紹下有參的await方法。
有參的await方法傳入兩個參數,一個是時間、另一個是時間機關
當調用有參的await方法時會出現下方兩個異常
java.util.concurrent.TimeoutException
java.util.concurrent.BrokenBarrierException
TimeoutException異常是指調用
await
方法後等待時間超過傳入的時間,此時會将
CyclicBarrier
的狀态變成broken,其他調用
await
方法将會抛出BrokenBarrierException異常,這時的
CyclicBarrier
将變得不可用,需要調用
reset()
方法重置
CyclicBarrier
的狀态。
為什麼這麼說?
源碼分析一波就可以看出來了
不管是有參還是無參的await方法都是調用
CyclicBarrier
的
dowait(boolean timed, long nanos)
方法,這個方法代碼太長了,截取部分貼出來
private int dowait(boolean timed, long nanos){
//加鎖、try catch代碼
final Generation g = generation;
//判斷栅欄的狀态
if (g.broken)
throw new BrokenBarrierException();
//...省略
int index = --count;
//(index == 0) 時的代碼,省略
for (;;) {
try {
if (!timed)
trip.await();
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {}
//判斷栅欄的狀态
if (g.broken)
throw new BrokenBarrierException();
if (g != generation)
return index;
//判斷是否是定時的,且已經逾時了
if (timed && nanos <= 0L) {
//打破栅欄的狀态
breakBarrier();
throw new TimeoutException();
}
}
//解鎖
}
在代碼的尾部進行判斷目前等待是否已經逾時,如果是會調用
breakBarrier()
方法,且抛出TimeoutException異常,下面是
breakBarrier()
的代碼
private void breakBarrier() {
generation.broken = true;
count = parties;
trip.signalAll();
}
代碼中将broken狀态置為true,表示目前栅欄移除損壞狀态,且重置栅欄數量,然後喚醒其他等待的線程。此時被喚醒的線程或者其他線程進入dowait方法時,都會抛出BrokenBarrierException異常
案例源代碼位址:
https://github.com/rainbowda/learnWay/tree/master/learnConcurrency/src/main/java/com/learnConcurrency/utils/cyclicBarrier覺得不錯的點個Star,謝謝