cyclicbarrier是java.util.concurrent包下面的一個工具類,字面意思是可循環使用(cyclic)的屏障(barrier),通過它可以實現讓一組線程到達一個屏障(也可以叫同步點)時被阻塞,直到最后一個線程到達屏障時,所有被屏障攔截的線程才會繼續執行。
這篇文章將介紹cyclicbarrier這個同步工具類的以下幾點
- 通過案例分析
- 兩種不同構造函數測試
- cyclicbarrier和countdownlatch的區別
- await方法及源碼分析。
需求
繼上一篇countdownlatch模擬游戲加載后,現在用戶點擊開始按鈕后,需要匹配包括自己在內的五個玩家才能開始游戲,匹配玩家成功后進入到選擇角色階段。當5位玩家角色都選擇完畢后,開始進入游戲。進入游戲時需要加載相關的數據,待全部玩家都加載完畢后正式開始游戲。
解決方案
從需求中可以知道,想要開始游戲需要經過三個階段,分別是
匹配玩家
選擇角色
加載數據
在這三個階段中,都需要互相等待對方完成才能繼續進入下個階段。
這時可以采用cyclicbarrier來作為各個階段的節點,等待其他玩家到達,在進入下個階段。
定義繼承runnable的類
這里名稱就叫做startgame,包含兩個屬性
1
2
|
private string player; private cyclicbarrier barrier; |
通過構造函數初始化兩個屬性
1
2
3
4
|
public startgame(string player, cyclicbarrier barrier) { this .player = player; this .barrier = barrier; } |
run方法如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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()等待使用
1
|
thread.sleep() |
來模擬花費時間
編寫測試代碼
cyclicbarrier有兩個構造函數,如下
1
2
|
public cyclicbarrier( int parties) {} public cyclicbarrier( int parties, runnable barrieraction) {} |
先來看看一個參數的構造函數
cyclicbarrier(int parties)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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);
替換為
1
2
3
4
5
6
7
8
9
10
|
cyclicbarrier barrier = new cyclicbarrier( 5 , () -> { try { system.out.println( "階段完成,等待2秒..." ); thread.sleep( 2000 ); system.out.println( "進入下個階段..." ); } catch (interruptedexception e) { e.printstacktrace(); } }); |
再來看看效果
可以看到在到達某個節點時,會執行實例化cyclicbarrier時傳入的runnable對象。而且每一次到達都會執行一次。
cyclicbarrier和countdownlatch的區別
countdownlatch | cyclicbarrier |
---|---|
計數為0時,無法重置 | 計數達到0時,計數置為傳入的值重新開始 |
調用countdown()方法計數減一,調用await()方法只進行阻塞,對計數沒任何影響 | 調用await()方法計數減一,若減一后的值不等于0,則線程阻塞 |
不可重復使用 | 可重復使用 |
await方法
1
2
|
public int await(){} public int await( long timeout, timeunit unit){} |
無參的await方法這里就不做介紹了,主要介紹下有參的await方法。
有參的await方法傳入兩個參數,一個是時間、另一個是時間單位
當調用有參的await方法時會出現下方兩個異常
1
2
|
java.util.concurrent.timeoutexception java.util.concurrent.brokenbarrierexception |
timeoutexception異常是指調用await方法后等待時間超過傳入的時間,此時會將cyclicbarrier的狀態變成broken,其他調用await方法將會拋出brokenbarrierexception異常,這時的cyclicbarrier將變得不可用,需要調用reset()方法重置cyclicbarrier的狀態。
為什么這么說?
源碼分析一波就可以看出來了
不管是有參還是無參的await方法都是調用cyclicbarrier的dowait(boolean timed, long nanos)方法,這個方法代碼太長了,截取部分貼出來
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
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()的代碼
1
2
3
4
5
|
private void breakbarrier() { generation.broken = true ; count = parties; trip.signalall(); } |
代碼中將broken狀態置為true,表示當前柵欄移除損壞狀態,且重置柵欄數量,然后喚醒其他等待的線程。此時被喚醒的線程或者其他線程進入dowait方法時,都會拋出brokenbarrierexception異常
案例源代碼地址:
原文鏈接:https://www.cnblogs.com/fixzd/p/9562525.html