一、作用
- 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控制柵欄的共同點。通過到達共同點後,刷新柵欄來達到重用的目的。
-------------------------------------------------------------------------------------------------------------------
知其然,更要知其所以然...
-------------------------------------------------------------------------------------------------------------------