【Java併發工具】CyclicBarrier

目錄

1 前言

2 示例

3 實現原理

3.1 成員變量

3.2 構造方法

3.3 await方法

3.4 reset方法

4 CyclicBarrier和CountDownLatch的區別


1 前言

本人使用jdk8版本。

CyclicBarrier的字面意思是可循環使用(Cyclic)的屏障(Barrier)。它要做的事情是,讓一組線程到達一個屏障(也可以叫同步點)時被阻塞,直到最後一個線程到達屏障時,屏障纔會開門,所有被屏障攔截的線程纔會繼續運行。在功能上與CountDownLatch非常相似,但也提供了一些CountDownLatch不具備的功能。CountDownLatch可以參考:CountDownLatch

CyclicBarrier 默認的構造方法是 CyclicBarrier(int parties),其參數表示 屏障攔截的線程數量,每個線程調用 await 方法告訴 CyclicBarrier 我已經到達了屏障,然後當前線程被阻塞。

2 示例

CyclicBarrier 提供一個高級的構造函數CyclicBarrier(int parties,Runnable barrierAction),用於在所有線程到達屏障時,優先執行barrierAction,方便處理更復雜的業務場景。

public static class CyclicBarrierTest2 {
    static CyclicBarrier c = new CyclicBarrier(2, new A());

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    c.await();
                } catch (Exception e) {
                }
                System.out.println(1);
            }
        }).start();
        try {
            c.await();
        } catch (Exception e) {
        }
        System.out.println(2);
    }

    static class A implements Runnable {
        @Override
        public void run() {
            System.out.println(3);
        }
    }
}

上面的執行結果爲:3,1,2或3,2,1。因爲在兩個線程到達屏障後,main線程和new的線程會同時被喚醒,之後的打印代碼誰先執行是不確定的。

3 實現原理

CyclicBarrier內部使用ReentrantLock和Condition來進行等待/通知,Condition參考:Condition接口——等待/通知工具

3.1 成員變量

    private final ReentrantLock lock = new ReentrantLock();
    // 用來使線程等待在lock上,同Object.wait()
    private final Condition trip = lock.newCondition();
    // 需等待的線程數量
    private final int parties;
    // 所有線程到達屏障點後率先執行的線程
    private final Runnable barrierCommand;
    // 需等待的線程數量,用來判斷是否爲0
    private int count;

3.2 構造方法

    public CyclicBarrier(int parties) {
        this(parties, null);
    }

    public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }

3.3 await方法

await()中主要執行的dowait(),所以這裏分析dowait()方法。首先要獲取鎖,然後判斷要等待的線程是否被中斷,是則整個屏障重置,所有已經等待的線程被喚醒。接着講count-1,判斷陷入等待的線程數量是否達到指定值,是則執行指定的結束線程,否則是通過Condition.await()使當前線程等待在lock上。下面只給出了關鍵代碼:

private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,TimeoutException {

        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // 若要等待的線程被中斷,則整個屏障重置,所有已經等待的線程被喚醒
            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }

            int index = --count;        // 剩下需要等待的線程數
            if (index == 0) {           // 所有線程都等待了,開啓屏障
                final Runnable command = barrierCommand;
                if (command != null)
                   command.run();        // command執行完成後的指定線程
            }

            // 在循環中通過Condition.await()使當前線程等待在lock上
            for (;;) {                    
                if (!timed)
                        trip.await();
                    else if (nanos > 0L)
                        nanos = trip.awaitNanos(nanos);
            }
        } finally {
            lock.unlock();
        }
}

3.4 reset方法

喚醒所有等待在lock上的線程,並將成員變量恢復初始值。

    public void reset() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // 重置屏障
            breakBarrier();   
            nextGeneration(); 
        } finally {
            lock.unlock();
        }
    }

    private void breakBarrier() {
        generation.broken = true;
        count = parties;
        trip.signalAll();
    }

4 CyclicBarrier和CountDownLatch的區別

  1. CountDownLatch的計數器只能使用一次,而CyclicBarrier的計數器可以使用reset()方法重置。
  2. CyclicBarrier提供了其它有用的方法,如:getNumberWaiting方法可以獲得CyclicBarrier阻塞的線程數量,isBroken()方法用來了解阻塞的線程是否被中斷。
  3. CyclicBarrier的高級構造方法能指定所有線程到達屏障後先執行的動作,能夠適應更復雜的使用場景。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章