CyclicBarrier是java.util.concurrent包下面的一個工具類,字面意思是可循環使用(Cyclic)的屏障(Barrier),經過它能夠實現讓一組線程到達一個屏障(也能夠叫同步點)時被阻塞,直到最後一個線程到達屏障時,全部被屏障攔截的線程纔會繼續執行。java
這篇文章將介紹CyclicBarrier這個同步工具類的如下幾點git
繼上一篇CountDownLatch
模擬遊戲加載後,如今用戶點擊開始按鈕後,須要匹配包括本身在內的五個玩家才能開始遊戲,匹配玩家成功後進入到選擇角色階段。當5位玩家角色都選擇完畢後,開始進入遊戲。進入遊戲時須要加載相關的數據,待所有玩家都加載完畢後正式開始遊戲。github
從需求中能夠知道,想要開始遊戲須要通過三個階段,分別是函數
在這三個階段中,都須要互相等待對方完成才能繼續進入下個階段。
這時能夠採用CyclicBarrier
來做爲各個階段的節點,等待其餘玩家到達,在進入下個階段。工具
這裏名稱就叫作StartGame
,包含兩個屬性源碼分析
private String player; private CyclicBarrier barrier;
經過構造函數初始化兩個屬性測試
public StartGame(String player, CyclicBarrier barrier) { this.player = player; this.barrier = barrier; }
run方法以下this
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()
來模擬花費時間3d
CyclicBarrier有兩個構造函數,以下
public CyclicBarrier(int parties) {} public CyclicBarrier(int parties, Runnable barrierAction) {}
先來看看一個參數的構造函數
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 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,則線程阻塞 |
不可重複使用 | 可重複使用 |
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異常
以爲不錯的點個Star,謝謝