CyclicBarrier源碼解析以及示例

概念

CyclicBarrier允許一組線程在到達某個柵欄點(common barrier point)互相等待,直到最後一個線程到達柵欄點,柵欄纔會打開,處於阻塞狀態的線程恢復繼續執行

源碼分析

構造函數

設置parties、count及barrierCommand屬性

public CyclicBarrier(int parties) {
    this(parties, null);
}
//當await的數量到達了設定的數量後,首先執行該Runnable對象。   
public CyclicBarrier(int parties, Runnable barrierAction) {
	if (parties <= 0) throw new IllegalArgumentException();
	this.parties = parties;
	this.count = parties;
	this.barrierCommand = barrierAction;
}

await()

public int await() throws InterruptedException, BrokenBarrierException {
    try {
        return dowait(false, 0L); //不超時等待
    } catch (TimeoutException toe) {
        throw new Error(toe); // cannot happen
    }
}

public int await(long timeout, TimeUnit unit)
    throws InterruptedException,
            BrokenBarrierException,
            TimeoutException {
    return dowait(true, unit.toNanos(timeout));
}

dowait(boolean timed, long nanos)

private int dowait(boolean timed, long nanos)
    throws InterruptedException, BrokenBarrierException,
            TimeoutException {
    // 獲取獨佔鎖
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 當前代
        final Generation g = generation;
        // 如果這代損壞了,拋出異常
        if (g.broken)
            throw new BrokenBarrierException();
    // 如果線程中斷了,拋出異常
    if (Thread.interrupted()) {
        // 將損壞狀態設置爲true
        // 並通知其他阻塞在此柵欄上的線程
        breakBarrier();
        throw new InterruptedException();
    }
 
    // 獲取下標
    int index = --count;
    // 如果是 0,說明最後一個線程調用了該方法
    if (index == 0) {  // tripped
        boolean ranAction = false;
        try {
            final Runnable command = barrierCommand;
            // 執行柵欄任務
            if (command != null)
                command.run();
            ranAction = true;
            // 更新一代,將count重置,將generation重置
            // 喚醒之前等待的線程
            nextGeneration();
            return 0;
        } finally {
            // 如果執行柵欄任務的時候失敗了,就將損壞狀態設置爲true
            if (!ranAction)
                breakBarrier();
        }
    }
 
    // loop until tripped, broken, interrupted, or timed out
    for (;;) {
        try {
             // 如果沒有時間限制,則直接等待,直到被喚醒
            if (!timed)
                trip.await();
            // 如果有時間限制,則等待指定時間
            else if (nanos > 0L)
                nanos = trip.awaitNanos(nanos);
        } catch (InterruptedException ie) {
            // 當前代沒有損壞
            if (g == generation && ! g.broken) {
                // 讓柵欄失效
                breakBarrier();
                throw ie;
            } else {
                // 上面條件不滿足,說明這個線程不是這代的
                // 就不會影響當前這代柵欄的執行,所以,就打個中斷標記
                Thread.currentThread().interrupt();
            }
        }
 
        // 當有任何一個線程中斷了,就會調用breakBarrier方法
        // 就會喚醒其他的線程,其他線程醒來後,也要拋出異常
        if (g.broken)
            throw new BrokenBarrierException();
 
        // g != generation表示正常換代了,返回當前線程所在柵欄的下標
        // 如果 g == generation,說明還沒有換代,那爲什麼會醒了?
        // 因爲一個線程可以使用多個柵欄,當別的柵欄喚醒了這個線程,就會走到這裏,所以需要判斷是否是當前代。
        // 正是因爲這個原因,才需要generation來保證正確。
        if (g != generation)
            return index;
        
        // 如果有時間限制,且時間小於等於0,銷燬柵欄並拋出異常
        if (timed && nanos <= 0L) {
            breakBarrier();
            throw new TimeoutException();
        }
    }
} finally {
    // 釋放獨佔鎖
    lock.unlock();
}
}

await的邏輯:如果該線程不是到達的最後一個線程,則他會一直處於等待狀態,除非有如下情況:

1 最後一個線程到達,即index == 0

2 超出了指定時間(超時等待)

3 其他的某個線程中斷當前線程

4 其他的某個線程中斷另一個等待的線程

5 其他的某個線程在等待barrier超時

6 其他的某個線程在此barrier調用reset()方法。reset()方法用於將屏障重置爲初始狀態。

應用場景

就比如我們在打匹配遊戲的時候,十個人必須全部加載到100%,纔可以開局。否則只要有一個人沒有加載到100%,那這個遊戲就不能開始。先加載完成的玩家必須等待最後一個玩家加載成功纔可以。

應用示例

public class CyclicBarrierDemo {
    private static CyclicBarrier cyclicBarrier;

    static class CyclicBarrierThread extends Thread{

        @Override
        public void run() {
            System.out.println("玩家 " + Thread.currentThread().getName() + " 加載100%");
            //等待
            try {
                cyclicBarrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args){
        cyclicBarrier = new CyclicBarrier(10, new Runnable() {
            public void run() {
                System.out.println("玩家都加載好了,開始遊戲....");
            }
        });

        for(int i = 0 ; i < 10 ; i++){
            new CyclicBarrierThread().start();
        }
    }
}

輸出

玩家 Thread-0 加載100%
玩家 Thread-2 加載100%
玩家 Thread-3 加載100%
玩家 Thread-6 加載100%
玩家 Thread-1 加載100%
玩家 Thread-4 加載100%
玩家 Thread-5 加載100%
玩家 Thread-8 加載100%
玩家 Thread-7 加載100%
玩家 Thread-9 加載100%
玩家都加載好了,開始遊戲....

CyclicBarrier和CountDownLatch的區別

1 CountDownLatch的計數器只能使用一次,而CyclicBarrier的計數器可以使用reset()方法重置,可以使用多次

2 CyclicBarrier還提供了一些其他有用的方法,比如getNumberWaiting()方法可以獲得CyclicBarrier阻塞的線程數量,isBroken()方法用來了解阻塞的線程是否被中斷

3 CountDownLatch允許一個或多個線程等待一組事件的產生,而CyclicBarrier用於等待其他線程運行到柵欄位置

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