Java的迴圈屏障-CyclicBarrier(譯)

徐家三少發表於2019-03-03

新書Java併發程式設計系統與模型已上線,歡迎拜讀。

前言

      barrier(屏障)與互斥量,讀寫鎖,自旋鎖不同,它不是用來保護臨界區的。相反,它跟條件變數一樣,是用來協同多執行緒一起工作的。
條件變數是多執行緒間傳遞狀態的改變來達到協同工作的效果。屏障是多執行緒各自做自己的工作,如果某一執行緒完成了工作,就等待在屏障那裡,直到其他執行緒的工作都完成了,再一起做別的事。舉個通俗的例子:
      1.對於條件變數。在接力賽跑裡,1號隊員開始跑的時候,2,3,4號隊員都站著不動,直到1號隊員跑完一圈,把接力棒給2號隊員,2號隊員收到接力棒後就可以跑了,跑完再給3號隊員。這裡這個接力棒就相當於條件變數,條件滿足後就可以由下一個隊員(執行緒)跑。
      2.對於屏障:在百米賽跑裡,比賽沒開始之前,每個運動員都在賽場上自由活動,有的熱身,有的喝水,有的跟教練談論。比賽快開始時,準備完畢的運動員就預備在起跑線上,如果有個運動員還沒準備完(除去特殊情況),他們就一直等,直到運動員都在起跑線上,裁判喊口號後再開始跑。這裡的起跑線就是屏障,做完準備工作的運動員都等在起跑線,直到其他運動員也把準備工作做完。

      java.util.concurrent.CyclicBarrier類是一個同步機制。它可以通過一些演算法來同步執行緒處理的過程。換言之,就是所有的執行緒必須等待對方,直到所有的執行緒到達屏障,然後繼續執行。下面這個圖可以說明:

Java的迴圈屏障-CyclicBarrier(譯)

                                                兩個執行緒等待迴圈屏障

      兩個執行緒通過呼叫CyclicBarrier的 await() 相互等待對方,一旦所有的執行緒都在CyclicBarrier中等待,然後所有的執行緒一起釋放然後繼續執行。

建立迴圈屏障

      當你要建立CyclicBarrier的時候必須指定在釋放他們前有多少個執行緒等待,下面是一個例子:

CyclicBarrier barrier = new CyclicBarrier(2);複製程式碼

在CyclicBarrier等待

      下面是在CyclicBarrier處等待:

barrier.await();複製程式碼

      你也可以指執行緒等待的超時時間,當等待超時的時候,執行緒依然會被釋放。即使並不是所有的執行緒都開始在CyclicBarrier等待。下面這行程式碼指定超時時間:

barrier.await(10, TimeUnit.SECONDS);複製程式碼

      所有執行緒在CyclicBarrier等待,是指:
       • 最後一個執行緒到達(呼叫await方法)

       • 一個執行緒被被另外一個執行緒中斷(另外一個執行緒呼叫了這個現場的interrupt()方法)

       • 其中一個等待的執行緒被中斷

       • 其中一個等待的執行緒超時

       • 一個外部的執行緒呼叫了yclicBarrier.reset()方法。

CyclicBarrier Action

      CyclicBarrier 支援一個Runnable屏障動作。當最後一個執行緒到達的時候,這個Runable物件就可以被執行。你需要將這個Runable屏障動作放置在他的構造器中,就像這樣:

Runnable      barrierAction = ... ;
CyclicBarrier barrier       = new CyclicBarrier(2, barrierAction);複製程式碼

下面這個例子說明了如何使用CyclicBarrier:

Runnable barrier1Action = new Runnable() {
    public void run() {
        System.out.println("BarrierAction 1 executed ");
    }
};
Runnable barrier2Action = new Runnable() {
    public void run() {
        System.out.println("BarrierAction 2 executed ");
    }
};

CyclicBarrier barrier1 = new CyclicBarrier(2, barrier1Action);
CyclicBarrier barrier2 = new CyclicBarrier(2, barrier2Action);

CyclicBarrierRunnable barrierRunnable1 =
        new CyclicBarrierRunnable(barrier1, barrier2);

CyclicBarrierRunnable barrierRunnable2 =
        new CyclicBarrierRunnable(barrier1, barrier2);

new Thread(barrierRunnable1).start();
new Thread(barrierRunnable2).start();
Here is the CyclicBarrierRunnable class:複製程式碼

      下面是CyclicBarrierRunnable類:

public class CyclicBarrierRunnable implements Runnable{

    CyclicBarrier barrier1 = null;
    CyclicBarrier barrier2 = null;

    public CyclicBarrierRunnable(
            CyclicBarrier barrier1,
            CyclicBarrier barrier2) {

        this.barrier1 = barrier1;
        this.barrier2 = barrier2;
    }

    public void run() {
        try {
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() +
                                " waiting at barrier 1");
            this.barrier1.await();

            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() +
                                " waiting at barrier 2");
            this.barrier2.await();

            System.out.println(Thread.currentThread().getName() +
                                " done!");

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}複製程式碼

      下面是控制檯的輸出。但是要注意有時候輸出的順序會發生變化,有時候是Thread-0先列印,有時候是Thread-1先列印。

Thread-0 waiting at barrier 1
Thread-1 waiting at barrier 1
BarrierAction 1 executed
Thread-1 waiting at barrier 2
Thread-0 waiting at barrier 2
BarrierAction 2 executed
Thread-0 done!
Thread-1 done!複製程式碼

小廣告

新書《Java併發程式設計系統與模型》目前已經寫完一部分。但是實際上說實話,並不知道讀者們對java併發系統有哪些比較關心的,而且閉門造車實在是太累,寫出來怕沒人看。所以我放在這裡徵求大家的意見。大家可以直接在評論裡提問或者希望作者重點描寫哪一部分內容,我好有針對性的寫。

相關文章