併發工具類源碼分析---CyclicBarrier(詳細)

一、作用

  •   JDK1.8英文註釋
* A synchronization aid that allows a set of threads to all wait for
* each other to reach a common barrier point.  CyclicBarriers are
* useful in programs involving a fixed sized party of threads that
* must occasionally wait for each other. The barrier is called
* <em>cyclic</em> because it can be re-used after the waiting threads
* are released.
  • 翻譯

其作用在於使一組線程相互等待直到到達某個共同點(類似於柵欄)。循環的意思爲這個柵欄可以被重複使用(即第一次等待所有線程到達共同點後,會重新初始化),後文會介紹。

二、實現分析

 

  • 問題一:多個線程相互等待?

在滿足什麼情況下等待(等待條件),如何等待(已知有Object.wait(),Condition.await()方法)。

  • 問題二:等待的線程何時被喚醒(共同點是什麼)?

在滿足什麼情況下喚醒(喚醒條件),如何喚醒(已知有Object.notifyAll(),Codition.signalAll()方法)。

  • 問題三:如何實現重複使用柵欄?

在滿足什麼情況下重新初始化柵欄(初始化條件),如何初始化(源碼採用Generation實現,請看後文)。

三、基本思想

假設N個線程相互等待,初始化count=N,每個線程判斷如果N!=0則進行等待並將N=N-1,如果某個線程判斷N=0,則喚醒所有等待的線程,並重新更新Barrier使之可以重複使用。

四、源碼分析

  • 屬性
/** The lock for guarding barrier entry */
//作用一:產生Condition對象  作用二:同步await()方法。
private final ReentrantLock lock = new ReentrantLock();

/** Condition to wait on until tripped */
//問題一問題二中等待喚醒採用Condition.await()/awaitNanos(long)和Condition.signalAll()方法。
private final Condition trip = lock.newCondition();

/** The number of parties */
//循換柵欄可以重複使用,那麼當再次進行初始化的時候,根據這個值進行初始化等待線程的個數(只是構造函數傳參的一個副本,不隨着await()方法的調用而遞減,相反的是count屬性)。
private final int parties;

/* The command to run when tripped */
//柵欄提供的額外功能,即當所有線程達到共同點後,由最後一個(count=0)達到的線程執行這個命令(先執行這個命令然後再喚醒其他線程),可以想一下使用場景。
private final Runnable barrierCommand;

/** The current generation */
//實現循環柵欄,表示當前柵欄所屬年代,當所有線程到達共同點後會用nextGeneration對柵欄進行重新初始化並更新年代。
private Generation generation = new Generation();

/**
 * Number of parties still waiting. Counts down from parties to 0
 * on each generation.  It is reset to parties on each new
 * generation or when broken.
 */
//表示還有多少線程在進行等待,每當一個線程調用await()方法之後,會減一,當爲0時喚醒其他等待線程。
private int count;
  • 構造函數
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;
}
  • 方法

//進行線程的等待,喚醒操作,返回剩餘等待的線程數。

public int await() throws InterruptedException, BrokenBarrierException {
    try {
        //主要調用dowait方法
        return dowait(false, 0L);
    } catch (TimeoutException toe) {
        throw new Error(toe); // cannot happen
    }
}
//timied:是否設置了等待超時 nanos:如果設置了超時,超時時間,納秒爲單位
private int dowait(boolean timed, long nanos)
    throws InterruptedException, BrokenBarrierException,
           TimeoutException {
    final ReentrantLock lock = this.lock;
    //這裏同步的目的有如下兩個原因
    //一:調用Condition.await()方法之前,該線程必須持有鎖,否則拋出IllegalMonitorStateException。
    //二:需要對count進行減一操作,多線程情況下要麼用CAS,要麼同步,在原因一的基礎上這裏用同步一併解決。
    lock.lock();
    try {
        //保存當前柵欄的年代,下文會用到
        final Generation g = generation;
        
       //當前柵欄處於broken狀態,可以通過源碼查看如果存在以下情況,則設置Generation .broken=true
       //一:當某一個線程被中斷;柵欄中如果某一線程被中斷,那麼其他等待的線程則會全部拋出BrokenBarrierException。
       //二:執行傳入的Runnable任務時拋出異常。
       //三:調用Barrier.reset()方法。
       //四:某個線程調用了await(long,TimeUnit)方法,且達到超時時間。
        if (g.broken)
            throw new BrokenBarrierException();
        //當前線程被中斷,則銷燬當前柵欄
        if (Thread.interrupted()) {
            breakBarrier();
            throw new InterruptedException();
        }
        //線程調用await()方法只有,對應的count=count-1,因爲用了同步,所以線程安全。
        int index = --count;
        //問題二的共同點
        if (index == 0) {  // tripped
            boolean ranAction = false;
            try {
                //所有線程到達共同點時,由最後一個線程(count=0)執行命令
                final Runnable command = barrierCommand;
                if (command != null)
                    command.run();
               //執行完命令設爲true,如果執行命令中拋出異常,那麼依然爲false。
                ranAction = true;
               //當執行完命令之後,喚醒所有等待線程,更新Barrier的generation,重新初始化barrier。
                nextGeneration();
                return 0;
            } finally {
                //當執行command命令拋出異常時,銷燬當前barrier。
                if (!ranAction)
                    breakBarrier();
            }
        }

        // loop until tripped, broken, interrupted, or timed out
        for (;;) {
            try {
                //經歷以上步驟,如果還沒有到達共同點,則讓當前線程在Condition上等待。
                if (!timed)
                    trip.await();
                else if (nanos > 0L)
                   //返回的nanos代表{@code nanosTimeout}值的估計值減去從該方法返回時等待的時間。一個正值可以用作對該方法的後續調用
                   //的參數,以完成所 需的等待時間。小於或等於零的值表示時間所剩無幾。
                    nanos = trip.awaitNanos(nanos);
            } catch (InterruptedException ie) {
                //線程被中斷,且還沒有達到共同點(因爲達到共同點之後g!=generation),如果barrier沒有被銷燬,則進行銷燬。
                if (g == generation && ! g.broken) {
                    breakBarrier();
                    throw ie;
                } else {
                    // We're about to finish waiting even if we had not
                    // been interrupted, so this interrupt is deemed to
                    // "belong" to subsequent execution.
                    //暫時未理解,後續補充...
                    Thread.currentThread().interrupt();
                }
            }

            if (g.broken)
                throw new BrokenBarrierException();
            //如果還未到達共同點,返回剩餘等待的線程個數。
            if (g != generation)
                return index;
          //如果設置了等待,且等待時間到達(看上面awaitNanos解釋),則銷燬柵欄。否則繼續for循環。
            if (timed && nanos <= 0L) {
                breakBarrier();
                throw new TimeoutException();
            }
        }
    } finally {
        lock.unlock();
    }
}
//銷燬柵欄,之後,所有等待的線程都會拋出BarrierBrokenExcepition

private void breakBarrier() {
    generation.broken = true;
    count = parties;
    trip.signalAll();
}
//當到達共同點後,喚醒所有等待線程並重新初始化柵欄,產生新的generation。
private void nextGeneration() {
    // signal completion of last generation
    trip.signalAll();
    // set up next generation
    count = parties;
    generation = new Generation();
}

以上就是CyclicBarrier源碼的分析,其主要是借用了ReentrantLock和Condition實現線程的協作(等待和喚醒),並通過count控制柵欄的共同點。通過到達共同點後,刷新柵欄來達到重用的目的。

-------------------------------------------------------------------------------------------------------------------

知其然,更要知其所以然...

-------------------------------------------------------------------------------------------------------------------

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