CyclicBarrier原始碼分析

辣雞小籃子發表於2020-09-08

CyclicBarrier原始碼分析

CyclicBarrier的作用是讓一組執行緒互相等待至某個狀態後並行執行(相對外部來說是並行,其實內部還是序列)

基本的使用方法是建立一個CyclicBarrier例項,並且指定parties的個數,然後執行緒依次呼叫CyclicBarrier的await()方法讓自己進入等待狀態,當最後一個執行緒進入await()方法時,將會喚醒所有正在等待的執行緒,並行執行。

CyclicBarrier雖然也是同步器,但是並非直接通過AQS來進行實現的,而是藉助了ReentrantLock以及Condition來進行實現。


CyclicBarrier的結構

public class CyclicBarrier {
    
    /**
     * 存在一個Generation靜態內部類
     */ 
    private static class Generation {
        boolean broken = false; // 標識CyclicBarrier是否被破壞
    }

    private final ReentrantLock lock = new ReentrantLock();
    
    private final Condition trip = lock.newCondition(); // 與ReentrantLock繫結的Condition例項

    private final int parties; // 用於記錄一共有多少個執行緒需要等待

    private final Runnable barrierCommand; // 由最後一個進入await()方法的執行緒進行呼叫

    private Generation generation = new Generation();

    private int count; // 用於記錄還需要多少個執行緒進行等待

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

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

    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));
    }
   
    private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        // ......
    }
    
    // 其他省略
}

可以看到CyclicBarrier存在全域性的lock、trip、parties、count、barrierCommand以及generation屬性,其中parties屬性用於記錄一共有多少個執行緒需要等待,而count用於記錄還需要多少個執行緒進行等待。

同時CyclicBarrier中定義了一個Generation靜態內部類,該內部類只有一個broken全域性屬性,用於標識CyclicBarrier是否被破壞,預設為false。

同時CyclicBarrier的構造方法會初始化全域性的parties、count以及barrierCommand屬性(CyclicBarrier初始化後,count的數量等於parties的數量)


await()方法

由於當建立CyclicBarrier例項之後,執行緒需要依次呼叫CyclicBarrier的await()方法讓自己進入等待狀態,因此從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));
}

await()方法存在兩個過載,區別是一個支援超時,一個不支援超時,最終都會呼叫dowait()方法(使用timed參數列示是否有超時限制,如果timed引數為true則需要傳遞具體的超時時間)


dowait()方法

private int dowait(boolean timed, long nanos)
    throws InterruptedException, BrokenBarrierException,
           TimeoutException {
    // 獲取全域性的ReentrantLock例項,並進行加鎖           
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 獲取全域性的Generation例項,如果Generation中的broken屬性為true則表示CyclicBarrier已經被破壞,則直接丟擲異常(預設是false)
        final Generation g = generation;

        if (g.broken)
            throw new BrokenBarrierException();

        // 如果執行緒已經被設定了中斷標識,則呼叫breakBarrier()方法,破壞CyclicBarrier
        if (Thread.interrupted()) {
            breakBarrier();
            throw new InterruptedException(); // 丟擲異常
        }

        // index屬性用於記錄還需要多少個執行緒進行等待
        int index = --count;
        // 如果index等於0,表示當前執行緒是最後一個進入await()方法的執行緒,如果barrierCommand不為空,那麼執行barrierCommand的run()方法,然後呼叫nextGeneration()方法,喚醒在指定Condition例項中等待的所有執行緒,並重置CyclicBarrier,然後執行緒直接返回,做自己的事情
        if (index == 0) {  // 最後一個執行緒走這個邏輯
            boolean ranAction = false;
            try {
                final Runnable command = barrierCommand;
                if (command != null)
                    command.run();
                ranAction = true;
                nextGeneration();
                return 0;
            } finally {
                // 如果在執行barrierCommand的run()方法時丟擲異常,那麼ranAction標識為false,那麼需要呼叫breakBarrier()方法,破壞CyclicBarrier
                if (!ranAction)
                    breakBarrier();
            }
        }

        // 如果非最後一個執行緒那麼將會往下執行
        
        // 迴圈
        for (;;) {
            try {
                // 如果沒有超時限制,那麼直接呼叫Condition例項的await()方法,讓執行緒在指定的Condition例項中進行等待,並釋放掉它擁有的鎖
                // 如果有超時限制,那麼呼叫Condition例項的awaitNanos()方法,至多讓執行緒在指定的Condition例項中等待指定的時間,該方法返回執行緒被喚醒後剩餘的毫秒數(超時返回小於等於0),並釋放掉它擁有的鎖
                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();
                }
            }

            // 當執行緒被喚醒後將會序列執行以下的邏輯
            
            // 如果發現CyclicBarrier被破壞了,那麼就丟擲異常
            if (g.broken)
                throw new BrokenBarrierException();

            // 正常情況下,當呼叫了nextGeneration()方法之後,generation引用就指向一個新的Generation例項,因此g!=generation,那麼執行緒直接返回,做自己的事情
            if (g != generation)
                return index;

            // 如果執行緒在Condition例項等待的過程中由於達到了超時時間而被喚醒了,那麼將會呼叫breakBarrier()方法,破壞CyclicBarrier
            if (timed && nanos <= 0L) {
                breakBarrier();
                throw new TimeoutException(); // 丟擲異常
            }
        }
    } finally {
        lock.unlock(); // 解鎖
    }
}

當執行緒進入dowait()方法後,需要獲取鎖,如果當前執行緒並非最後一個進入await()方法的執行緒,那麼將會在指定的Condition例項中進行等待,然後釋放掉它擁有的鎖,如果當前執行緒是最後一個進入await()方法的執行緒(index==0,表示還需要0個執行緒進行等待),如果barrierCommand不為空,那麼將會執行barrierCommand的run()方法,最後呼叫nextGeneration()方法。

如果在執行dowait()方法的過程中,執行緒已經被設定了中斷標識,或者最後一個執行緒在執行barrierCommand的run()方法時丟擲異常,或者在指定Condition例項等待的執行緒由於達到了超時時間而被喚醒,那麼都會呼叫breakBarrier()方法。


nextGeneration()方法

private void nextGeneration() {
    // 喚醒在指定Condition例項中等待的所有執行緒
    trip.signalAll();
    // 將count的數量設定成parties
    count = parties;
    // 將generation引用指向一個新的Generation例項
    generation = new Generation();
}

nextGeneration()方法用於指向下一個Generation,該方法將會喚醒在指定Condition例項中等待的所有執行緒,然後將count的數量設定成parties,恢復成CyclicBarrier初始化後的狀態,最後將generation引用指向一個新的Generation例項。


breakBarrier()方法

private void breakBarrier() {
    // 將Generation例項的broken屬性設定為true,表示CyclicBarrier已經被破壞
    generation.broken = true;
    // 將count的數量設定回parties
    count = parties;
    // 喚醒在指定Condition例項中等待的所有執行緒
    trip.signalAll();
}

breakBarrier()方法用於破壞CyclicBarrier,將Generation例項的broken屬性設定為true,表示CyclicBarrier已經被破壞,然後將count的數量設定成parties,最後喚醒在指定Condition例項中等待的所有執行緒。


reset()方法

public void reset() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        breakBarrier(); // 感覺是多餘的
        nextGeneration(); // 指向下一個Generation
    } finally {
        lock.unlock();
    }
}

reset()方法用於重置CyclicBarrier,其根本是將generation引用指向一個新的Generation例項。


流程總結

1.當建立了一個CyclicBarrier例項之後,執行緒需要依次呼叫CyclicBarrier的await()方法,讓自己進入等待狀態。

2.await()方法又會呼叫dowait()方法,當執行緒進入dowait()方法後,需要獲取鎖,如果當前執行緒並非最後一個進入await()方法的執行緒,那麼將會在指定的Condition例項中進行等待,然後釋放掉它擁有的鎖,如果當前執行緒是最後一個進入await()方法的執行緒(index==0,表示還需要0個執行緒進行等待),如果barrierCommand不為空,那麼將會執行barrierCommand的run()方法,最後呼叫nextGeneration()方法。

3.nextGeneration()方法用於指向下一個Generation,該方法將會喚醒在指定Condition例項中等待的所有執行緒,然後將count的數量設定成parties,恢復成CyclicBarrier初始化後的狀態,最後將generation引用指向一個新的Generation例項,當最後一個執行緒執行完nextGeneration()方法後,將會直接返回,做自己的事情,最後釋放掉它擁有的鎖。

4.當被喚醒的執行緒依次獲取到鎖後,將會繼續往下執行,如果判斷到generation引用已經指向一個新的Generation例項,那麼直接返回,做自己的事情,最後釋放掉它擁有鎖。

5.如果在執行dowait()方法的過程中,執行緒已經被設定了中斷標識,或者最後一個執行緒在執行barrierCommand的run()方法時丟擲異常,或者在指定Condition例項等待的執行緒由於達到了超時時間而被喚醒,那麼都會呼叫breakBarrier()方法,破壞CyclicBarrier,將Generation例項的broken屬性設定為true,表示CyclicBarrier已經被破壞,然後將count的數量設定成parties,最後喚醒在指定Condition例項中等待的所有執行緒。

6.當被喚醒的執行緒依次獲取到鎖後,將會繼續往下執行,如果判斷到Generation例項的broken屬性被設定了true,也就是CyclicBarrier已經被破壞,那麼將會直接丟擲異常,最後釋放掉它擁有的鎖。

7.當CyclicBarrier被破壞後是不能夠進行復用的,因為Generation的broken屬性已經被設定成true,因此需要先呼叫一次reset()方法進行重置。


FAQ

為什麼說CyclicBarrier是可以複用的?

因為當最後一個執行緒進入await()方法,將會呼叫nextGeneration()方法,該方法除了喚醒在指定Condition中等待的執行緒之外,還會將count的數量設定成parties,恢復成CyclicBarrier初始化後的狀態,同時將generation引用指向一個新的Generation例項,因此CyclicBarrier是可以複用的,同時需要注意的是,如果CyclicBarrier已經被破壞,那麼需要先呼叫一次reset()方法之後才能夠進行復用。

相關文章