Java併發程式設計之CyclicBarrier使用指南

emoo發表於2019-03-04

一、CyclicBarrier能做什麼事情

和CountDownLatch一樣,CyclicBarrier也是java.util.concurrent包下的一個類;從類名就可以看出,這是一個可以迴圈使用(Cylcic)的屏障(Barrier),所做的事情就是讓一組執行緒到達一個屏障(同步點)時被阻塞,直到這組執行緒中的最後一個到達屏障時,屏障才會開啟,之前阻塞的執行緒繼續執行。過程如下圖所示

CyclicBarrier執行圖

上圖中的三個執行緒中各有一個barrier.await,任何一個執行緒在執行到barrier.await時都會進入阻塞等待狀態,直到三個執行緒都到了barrier.await時才從await返回,繼續向後執行。

二、CyclicBarrier  如何使用

例項化CyclicBarrier物件時通過它的建構函式設定屏障要攔截的執行緒(呼叫barrier.await的次數)的資料量,每個執行緒通過呼叫CyclicBarrier例項的await方法告訴CyclicBarrier我已經到達屏障,並將自己阻塞。

此外,如果在構造CyclicBarrier時設定了一個Runnable實現,那麼最後一個barrier.await 的執行緒會執行這個方法,以完成一些預設工作

CyclicBarrier經常用於多執行緒計算資料,最後要將計算結果合併的場景。例如一個Excel表記錄了使用者一個季度所有的銀行流水,每個sheet記錄了該使用者每個月的銀行流水情況,要統計該使用者整個季度的銀行流水狀況時,可以先使用多執行緒統計每個sheet的銀行流水,都執行完畢後,使用每個執行緒的計算結果來計算出該使用者整個季度的銀行流水狀況。

public class CyclicBarrierTest implements Runnable{

    /*建立一個CyclicBarrier例項,屏障資料設為3,處理完之後執行當前類的run方法*/
    private CyclicBarrier cb = new CyclicBarrier(3,this);

    /*建立執行緒池,只有三個月的資料,所以只需三個執行緒*/
    private Executor executor = Executors.newFixedThreadPool(3);

    /*建立一個ConcurrentHashMap,用來儲存每個sheet計算出的結果*/
    private ConcurrentHashMap<String,Integer> sheetBankWaterCount = new ConcurrentHashMap<String, Integer>();

    public void count() {
        for(int i = 0;i<3;i++){

            /*每個執行緒用來處理單個sheet中的任務*/
            executor.execute(new Runnable() {

                public void run() {

                    /*此處加入複雜的邏輯處理程式碼*/
                    sheetBankWaterCount.put(Thread.currentThread().getName(),1);

                    try {

                        /*執行緒完成工作後呼叫await 設定屏障*/
                        cb.await();
                    }catch (BrokenBarrierException e){
                        e.printStackTrace();
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }

                }
            });
        }
    }


    /*等到所有的執行緒到達屏障*/

    public void run() {
        int res = 0;
        /*根據之前多執行緒的結果計算出整個季度的銀行流水*/
        for (Map.Entry<String,Integer> sheet: sheetBankWaterCount.entrySet()) {
            res += sheet.getValue();
        }

        sheetBankWaterCount.put("result",res);
        /*將結果輸出*/
        System.out.println(res);
    }

    public static void main(String[] args){

        CyclicBarrierTest test = new CyclicBarrierTest();

        /*注意,此時不需要呼叫test.run(),最後一個await方法會呼叫run方法*/
        test.count();
    }
}

複製程式碼

三、使用CyclicBarrier時要注意的問題

線上程池中使用CyclicBarrier時一定要注意執行緒的數量要多於CyclicBarrier例項中設定的阻塞執行緒的數量就會發生死鎖。
呼叫await()方法的次數一定要等於屏障中設定的阻塞執行緒的數量,否則也會死鎖。

四、CyclicBarrier和CountDownLatch的區別

  1. 首先二者都能讓一個或多個執行緒阻塞等待,都可以用在多個執行緒間的協調,起到執行緒同步的作用。但CountDownLatch是多個執行緒都進行了countDown之後才會觸發時間,喚醒await在latch上的執行緒,執行完countDown操作之後會繼續自己執行緒的工作。而CyclicBarrier是一個柵欄,用於同步所有呼叫await方法的執行緒,等到所有的方法都執行了await方法後,所有的執行緒才會返回各自執行自己的工作。
  2. CountDownLatch計數器只能使用一次,而CyclicBarrier的計數器可以呼叫 reset() 方法重置,能處理更加複雜的業務場景。

java併發之CountDownLatch使用方法

Java併發程式設計之Semaphore使用指南

相關文章