java多線程開發(fā)之通過對戰(zhàn)游戲?qū)W習(xí)CyclicBarrier
CyclicBarrier是java.util.concurrent包下面的一個工具類,字面意思是可循環(huán)使用(Cyclic)的屏障(Barrier),通過它可以實(shí)現(xiàn)讓一組線程到達(dá)一個屏障(也可以叫同步點(diǎn))時被阻塞,直到最后一個線程到達(dá)屏障時,所有被屏障攔截的線程才會繼續(xù)執(zhí)行。
這篇文章將介紹CyclicBarrier這個同步工具類的以下幾點(diǎn)
- 通過案例分析
- 兩種不同構(gòu)造函數(shù)測試
- CyclicBarrier和CountDownLatch的區(qū)別
- await方法及源碼分析。
需求
繼上一篇CountDownLatch模擬游戲加載后,現(xiàn)在用戶點(diǎn)擊開始按鈕后,需要匹配包括自己在內(nèi)的五個玩家才能開始游戲,匹配玩家成功后進(jìn)入到選擇角色階段。當(dāng)5位玩家角色都選擇完畢后,開始進(jìn)入游戲。進(jìn)入游戲時需要加載相關(guān)的數(shù)據(jù),待全部玩家都加載完畢后正式開始游戲。
解決方案
從需求中可以知道,想要開始游戲需要經(jīng)過三個階段,分別是
匹配玩家
選擇角色
加載數(shù)據(jù)
在這三個階段中,都需要互相等待對方完成才能繼續(xù)進(jìn)入下個階段。
這時可以采用CyclicBarrier來作為各個階段的節(jié)點(diǎn),等待其他玩家到達(dá),在進(jìn)入下個階段。
定義繼承Runnable的類
這里名稱就叫做StartGame,包含兩個屬性
private String player; private CyclicBarrier barrier;
通過構(gòu)造函數(shù)初始化兩個屬性
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()+" 進(jìn)行選擇角色..."); choiceRole(); System.out.println(this.getPlayer()+" 角色選擇完畢等待其他玩家..."); barrier.await(); System.out.println(this.getPlayer()+" 開始游戲,進(jìn)行游戲加載..."); loading(); System.out.println(this.getPlayer()+" 游戲加載完畢等待其他玩家加載完成..."); barrier.await(); start(); } catch (Exception e){ e.printStackTrace(); } }
其他的方法findOtherPlayer()、choiceRole()等待使用
Thread.sleep()
來模擬花費(fèi)時間
編寫測試代碼
CyclicBarrier有兩個構(gòu)造函數(shù),如下
public CyclicBarrier(int parties) {} public CyclicBarrier(int parties, Runnable barrierAction) {}
先來看看一個參數(shù)的構(gòu)造函數(shù)
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(); }
測試結(jié)果如下
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("進(jìn)入下個階段..."); } catch (InterruptedException e) { e.printStackTrace(); } });
再來看看效果
可以看到在到達(dá)某個節(jié)點(diǎn)時,會執(zhí)行實(shí)例化CyclicBarrier時傳入的Runnable對象。而且每一次到達(dá)都會執(zhí)行一次。
CyclicBarrier和CountDownLatch的區(qū)別
CountDownLatch | CyclicBarrier |
---|---|
計數(shù)為0時,無法重置 | 計數(shù)達(dá)到0時,計數(shù)置為傳入的值重新開始 |
調(diào)用countDown()方法計數(shù)減一,調(diào)用await()方法只進(jìn)行阻塞,對計數(shù)沒任何影響 | 調(diào)用await()方法計數(shù)減一,若減一后的值不等于0,則線程阻塞 |
不可重復(fù)使用 | 可重復(fù)使用 |
await方法
public int await(){} public int await(long timeout, TimeUnit unit){}
無參的await方法這里就不做介紹了,主要介紹下有參的await方法。
有參的await方法傳入兩個參數(shù),一個是時間、另一個是時間單位
當(dāng)調(diào)用有參的await方法時會出現(xiàn)下方兩個異常
java.util.concurrent.TimeoutException java.util.concurrent.BrokenBarrierException
TimeoutException異常是指調(diào)用await方法后等待時間超過傳入的時間,此時會將CyclicBarrier的狀態(tài)變成broken,其他調(diào)用await方法將會拋出BrokenBarrierException異常,這時的CyclicBarrier將變得不可用,需要調(diào)用reset()方法重置CyclicBarrier的狀態(tài)。
為什么這么說?
源碼分析一波就可以看出來了
不管是有參還是無參的await方法都是調(diào)用CyclicBarrier的dowait(boolean timed, long nanos)方法,這個方法代碼太長了,截取部分貼出來
private int dowait(boolean timed, long nanos){ //加鎖、try catch代碼 final Generation g = generation; //判斷柵欄的狀態(tài) 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) {} //判斷柵欄的狀態(tài) if (g.broken) throw new BrokenBarrierException(); if (g != generation) return index; //判斷是否是定時的,且已經(jīng)超時了 if (timed && nanos <= 0L) { //打破柵欄的狀態(tài) breakBarrier(); throw new TimeoutException(); } } //解鎖 }
在代碼的尾部進(jìn)行判斷當(dāng)前等待是否已經(jīng)超時,如果是會調(diào)用breakBarrier()方法,且拋出TimeoutException異常,下面是breakBarrier()的代碼
private void breakBarrier() { generation.broken = true; count = parties; trip.signalAll(); }
代碼中將broken狀態(tài)置為true,表示當(dāng)前柵欄移除損壞狀態(tài),且重置柵欄數(shù)量,然后喚醒其他等待的線程。此時被喚醒的線程或者其他線程進(jìn)入dowait方法時,都會拋出BrokenBarrierException異常
案例源代碼地址:
相關(guān)文章
Java Socket實(shí)現(xiàn)多人聊天系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了Java Socket實(shí)現(xiàn)多人聊天系統(tǒng),具有圖形界面,實(shí)現(xiàn)文件傳輸功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-07-07用java開發(fā)dota英雄最華麗的技能(實(shí)例講解)
下面小編就為大家分享一篇使用java開發(fā)dota英雄最華麗的技能實(shí)例,具有非常好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2017-11-11經(jīng)典的Java面試題及回答集錦(基礎(chǔ)篇)
本文給大家收藏整理了java面試題及回答,非常不錯,具有參考借鑒價值,需要的朋友參考下吧2018-03-03JDK1.8中ConcurrentHashMap中computeIfAbsent死循環(huán)bug問題
這篇文章主要介紹了JDK1.8中ConcurrentHashMap中computeIfAbsent死循環(huán)bug,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08Java map.getOrDefault()方法的用法詳解
這篇文章主要介紹了Java map.getOrDefault()方法的用法詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12maven項目在實(shí)踐中的構(gòu)建管理之路的方法
這篇文章主要介紹了maven項目在實(shí)踐中的構(gòu)建管理之路的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-05-05