戲說java多線程之CyclicBarrier(循環柵欄)的CyclicBarrier(int parties)構造方法

  CyclicBarrier是JDK 1.5 concurrent包出現的一個用於解決多條線程阻塞,當達到必定條件時一塊兒放行的一個類。咱們先來看這樣一個簡單的需求。java

  如今我有一個寫入數據的類,繼承Runable接口:多線程

public class WriteDateThread implements Runnable {

    @Override
    public void run() {

        System.out.println(Thread.currentThread().getName() + "開始寫入數據...");
        
        System.out.println(Thread.currentThread().getName() + "寫入數據完畢!");
    }
}

  代碼很簡單,只有兩個輸出語句,一個是開始寫入數據,而後打印寫入完畢。ide

  而後建立這樣一個Main函數的類:函數

public class CyclicBarrierMain {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            WriteDateThread thread = new WriteDateThread();
            Thread t = new Thread(thread);
            t.start();
        }
    }
}

  一樣,也是很簡單的代碼,我如今經過main函數來啓動這樣5個WriteDateThread線程,想必你們應該清楚線程之間的執行順序在於誰搶到了CPU執行權,因而執行結果以下:this

  

  咱們可看到,每條線程的開始執行和執行完畢和其餘線程都是交錯的,這也符合了多線程的執行規律。咱們來想象一個實際中的需求,如今有5個玩家同時進入一場遊戲對決當中,系統須要讀取玩家的數據,當讀取完成後,容許玩家進入遊戲中。按理來講,當系統讀取完某玩家的數據後,該玩家就擁有入場權限了,若是咱們不加任何干預的話,會發生什麼狀況呢?正如上圖中執行結果那樣,系統讀取0,2號玩家的數據,而後2號玩家讀取完畢,因而2號玩家先進場,而後再讀取4,1號玩家的數據...以此類推。我想你們確定玩過LOL或者農藥這類的多人競技遊戲,實際狀況是這樣的嗎?不是!實際狀況是,系統加載完某玩家的數據後,會讓該玩家等待其餘玩家的數據加載,當全部玩家的數據都加載完畢後,你們同時進入遊戲(這裏不考慮掉線等意外狀況)。spa

  好了,有了這樣一個實際案例,我說出如今的需求你們就很容易理解了,如今的需求是要求全部線程在執行完寫入數據後,不能在向下執行,而是等待全部其餘線程執行完寫入數據,而後你們再一塊兒執行「寫入數據完畢...」,咱們固然能夠用java.util.concurrent包下CountDownLatch計數器來自行控制,可是這裏我但願用更智能化一些的CyclicBarrier(循環柵欄)來完成這個需求:線程

  代碼以下:3d

 1 public class WriteDateThread implements Runnable {
 2 
 3     private CyclicBarrier barrier;
 4     
 5     public WriteDateThread(CyclicBarrier barrier) {
 6         this.barrier = barrier;
 7     }
 8 
 9     @Override
10     public void run() {
11         System.out.println(Thread.currentThread().getName() + "開始寫入數據...");
12         try {
13             barrier.await(); //經過調用await方法在此處設置柵欄,使得線程執行到此進入阻塞狀態等待其餘線程執行完成。
14         } catch (InterruptedException | BrokenBarrierException e) {
15             e.printStackTrace();
16         }
17         System.out.println(Thread.currentThread().getName() + "寫入數據完畢!");
18     }
19 }
 1 public class CyclicBarrierMain {
 2     public static void main(String[] args) {
 3         CyclicBarrier barrier = new CyclicBarrier(5);
 4         for (int i = 0; i < 5; i++) {
 5             WriteDateThread thread = new WriteDateThread(barrier);
 6             Thread t = new Thread(thread);
 7             t.start();
 8         }
 9     }
10 }

  咱們經過調用CyclicBarrier的await方法,使線程執行到此的時候,進入到阻塞狀態,而且等待其餘線程都執行到此後,再統一放行,讓它們同時向下繼續執行(放行後具體誰先執行,仍是要看誰先搶到CPU執行權)code

  運行結果:blog

  

  如此,便獲得了咱們需求中想要的結果了。

  CyclicBarrier的用法到此就介紹完畢,下面我着重介紹一下,CyclicBarrier最經常使用的一個構造函數,也是我第一次使用時很是令我困惑的一個點,就是我上面代碼CyclicBarrierMain中第3行CyclicBarrier barrier = new CyclicBarrier(int parties);這個構造器。

  咱們如今有5條線程,所以我傳入5這毫無疑問,可是這個5到底是什麼意思呢?若是傳入的值大於5或者小於5又會是什麼結果呢?你們不妨能夠自行試驗一下,只要咱們傳入的不是5,程序都會一直處於阻塞狀態,停不下來。

  我翻看過別人別這個值的解釋,比較抽象,原文以下:

  參數parties指定線程數量,當指定的線程值都到達柵欄點時,柵欄打開,線程恢復。須要注意的是,當指定的線程數量大於啓動的線程數量,好比修改上例中的代碼,只啓動4個線程,那麼全部的線程將執行到await處而後一直處於等待狀態。第二種狀況是指定的線程數量小於啓動的線程,上例代碼,啓動6個線程,那麼當第5個線程到達柵欄點時,那麼這5個線程就會恢復繼續執行,而第6個線程將一直處於阻塞狀態。

  我將經過一個小故事來講明這個問題。

  故事是這樣的,有這樣一場騎馬比賽,有5名騎手,他們各自擁有本身的賽道,而且每條賽道上在開賽以前就已經預設好了一個柵欄。另外還有一名柵欄管理員和一名指揮官,指揮官只負責向管理員發號施令,他只會告訴管理員一個數字n。這個數字的意思是,管理員要打開的柵欄的數量,前提是他必須看到有n個騎手都到達柵欄前,纔會將這n個騎手面前的柵欄打開。因而管理會記着這個數字n在柵欄旁守着,當有1名、2名...騎手到達柵欄時,管理員不予理會,直到有第n名騎手到達柵欄處時,管理員統一打開柵欄放行,騎手們得以同時繼續騎行,而管理員則完成了任務,進屋休息去了。

  若是指揮官告訴管理員的數字是5,那麼一切和平的進行,不會發生任何問題。可是若是指揮官,告訴管理員的數字不是5,好比說4或者6。這樣問題就產生了,對於這兩種狀況,咱們分別來分析一下:

  • 指揮官給出的數字大於騎手數量,這裏咱們假設是6

  我前邊說過,每條賽道上在開賽以前都預設好了柵欄,那麼此時管理從指揮官那裏獲得的數字是6,所以管理員要執行的任務就是看着柵欄,直到第1名騎手到達,第2名騎手到達,第3名...直到管理員看見第6名騎手到達時才進行統一放行,可是問題是,總共的參賽選手只有5名啊!這下好了,管理員是個死心眼,他不看見第6名騎手他是絕對不會進行放行的,那麼你們能夠想象一下,5名騎手被卡在柵欄前不能繼續後面的比賽,而管理員在那裏癡癡的等着第6名騎手的到來,就這樣一直到天荒地老...

  • 指揮官給出的數字小於騎手數量,這裏咱們假設是4

  還像剛纔同樣,管理拿到數字4後便在柵欄旁等着,直到看見騎手的相繼到來,第1名,第2名,第3名,第4名!管理員看見第4名騎手到來後,當即打開他們各自賽道前的柵欄,因而這4名騎手繼續後面的比賽,管理員也完成了本身的任務,進屋睡覺去了...等等!還有第5名騎手呢!第5名騎手到達柵欄前驚奇的發現,其餘賽道都被放行了,可是他的賽道上還有柵欄,他不得不等下等着管理員放行,可是鬱悶的是,他並不知道管理員不再會回來了,因而他就這樣孤獨的一直等待着...

  

  好了,故事講完了,如今咱們經過這個故事映射到代碼當中,騎手其實就是咱們開的線程,他們各自賽道上賽前設置的柵欄,其實就是咱們在程序執行前調用的CyclicBarrier的await方法,程序執行到這個柵欄處會被阻塞住。指揮官其實就是咱們本身(寫程序的人),管理員就是CyclicBarrier內部實現喚醒線程的解決方案,咱們(指揮官)給管理員發號的施令(一個數字)就是咱們經過CyclicBarrier(int parties)傳入的數值。

  若是咱們傳入的是6,由於咱們只有5條線程,第6條線程永遠不會到來,所以管理員也就永遠不會放行柵欄,全部線程將會阻塞在柵欄出等待那不存在的第6條線程,咱們更改代碼中傳入的數值爲6,獲得的運行結果:

  

  能夠看到,5條線程所有阻塞到了調用await方法的地方,而且程序一直處於阻塞狀態。

  

  若是咱們傳入的是4,管理員在看到第4條線程的到來時,就會放行他們4個面前的柵欄,因而這4條線程繼續執行,此時管理員完成任務回家睡覺了,第5條線程到來時,會一直卡在柵欄前。咱們更改代碼中傳入的數值爲4,獲得的運行結果:

  

  

  經過上述的通俗的講解,相信你們不會在以爲CyclicBarrier的(int parties)構造方法那麼晦澀難懂了,因爲本人所知有限,故難保證文中有什麼錯誤之處,歡迎大牛不惜吝嗇下方留言指正。

相關文章
相關標籤/搜索