文章目錄
- 前言
- 與CountDownLatch的區別
- 重要成員
- 構造器
- 輔助方法
- await
- 從問題分析深入理解CyclicBarrier
- 有幾種線程在執行?
- 一代線程們通過barrier的完整流程是什麼?
- 前n-1個線程發生中斷或超時的流程是什麼?
- 第n個線程在執行nextGeneration()之前,前面的線程中斷或超時會怎麼樣?
- 第n個線程在執行nextGeneration()後,前面的線程中斷或超時會怎麼樣?
- g == generation成立,意味着什麼?
- CyclicBarrier使用的非公平鎖有什麼影響?
- 第n+1個線程獲得到的Generation局部變量,會不會是第一代的?
- 條件隊列上的node的線程,肯定是同一“代”的嗎?
- 同步隊列上的node的線程,肯定是同一“代”的嗎?
- 如果generation成員已經broken,該怎麼辦?
- 調用await(0, TimeUnit.SECONDS)可能導致TimeoutException?
- 總結
前言
上一篇文章CountDownLatch源碼解析我們學習了CountDownLatch,接下來我們來分析一下和它用法很類似的CyclicBarrier。
與CountDownLatch的區別
在CountDownLatch中,有兩種毫無關係的線程:一是執行countDown
的線程,用來減小計數器count,肯定不會阻塞;二是執行await
的線程,用來阻塞等待直到count爲0。
但在CyclicBarrier中,每個執行await
的線程都將充當上面兩種角色,即先令count減一再阻塞等待。
我們舉個栗子,比如山地景區裏的纜車,每個纜車可以裝4個人,爲了不浪費位置,前3個人都需要等待第4個人的到來(線程先阻塞),只有每次都要等到齊了4個人後,才讓他們一起上纜車(喚醒前面阻塞的線程),當然在上纜車之前第4個人要負責給大家拴好安全帶(執行構造器傳入的barrierAction
參數)後才讓大家上纜車。
重要成員
private static class Generation {
boolean broken = false;
}
/** The lock for guarding barrier entry */
private final ReentrantLock lock = new ReentrantLock();
/** Condition to wait on until tripped */
private final Condition trip = lock.newCondition();
/** The number of parties */
private final int parties;
/* The command to run when tripped */
private final Runnable barrierCommand;
/** The current generation */
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.
*/
private int count;
我們將成員分成三組:
1.計數器相關:
private final int parties;
private int count;
parties
成員。代表每次通過barrier一共需要的線程數量,它是final
的,被構造器所賦值被不再改變,因爲每次通過barrier後需要靠它重新復位計數器。count
成員。Number of parties still waiting
,當前還需要多少個線程到達barrier,它的初始值是parties
,通過barrier時它肯定變成了0。- 簡單的說,到達barrier是指,CyclicBarrier的使用者因調用
await
而阻塞;通過barrier是指,CyclicBarrier的使用者從await
處返回。
2.同步組件相關:
private final ReentrantLock lock = new ReentrantLock();
private final Condition trip = lock.newCondition();
CyclicBarrier的線程同步是依賴於 ReentrantLock 和 Condition 的。到達barrier的線程開始都會進入condition queue
後阻塞,等待signal的到來;這些線程被喚醒後,將進入sync queue
中排隊搶鎖,搶到鎖的線程才能退出CyclicBarrier#await
。
所以看完本文也就相當於對Condition的實現進行了一遍複習,閱讀本文也需要讀者比較瞭解Condition的實現。
3.CyclicBarrier自定義需要:
private Generation generation = new Generation();
private final Runnable barrierCommand;
CyclicBarrier是可以重複使用的,每一組一起通過barrier的線程我們稱它們爲一代Generation
,這就像是一種版本標記。在AtomicStampedReference源碼裏面,我們通過一個int值來代表當前的版本,而Generation
卻不需要有一個int值成員來作爲版本信息,因爲比較的時候,我們都是直接通過g1 == g2
比較兩個Generation
對象的地址值來判斷版本是否爲同一個版本,也就是說,每new一次Generation
就代表是新的一代。
barrierCommand
是每一代線程們通過barrier之前要做的事情,它雖然是一個Runnable
對象,但不要誤會,我們執行它的run方法時根本不會新起一個線程來執行,這裏你甚至可以將這句的Runnable
隨便變成一個具有run方法的類都可以。當然,barrierCommand
可以爲null,這說明通過barrier不需要做什麼事情。
構造器
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;
}
它有兩個構造器,第一個版本也是調用第二個,但沒有傳入那個Runnable
對象而已。
可見CyclicBarrier的計數器必須至少爲1。如果可以爲0,也沒有意義。
輔助方法
在講解await()
之前,先講解一下這幾個輔助方法,順便加深理解CyclicBarrier的自定義成員。
nextGeneration
前面提到,每一組一起通過barrier的線程我們稱它們爲一代Generation
,每new一次Generation
對象就代表是新的一代。在CyclicBarrier中,我們使用nextGeneration
來開啓新的一代,自然它裏面也會去newGeneration
對象。
private void nextGeneration() {
// 喚醒條件隊列上的這一代線程
trip.signalAll();
// 恢復count值,因爲需要重新計數
count = parties;
generation = new Generation();
}
nextGeneration
用來開啓新的一代,它除了new了Generation
,還需要喚醒條件隊列上的這一代線程,恢復count值以重新計數。正常流程下,一代中的最後一個調用CyclicBarrier#await
的線程將執行此方法。
既然執行了Condition#signalAll
方法,說明執行nextGeneration
之前就已經獲得了lock鎖,而且nextGeneration
的末尾也沒有執行ReentrantLock#unlock
方法,這說明,nextGeneration
的整個過程都是持有鎖的,不用擔心多線程訪問的問題了。(看來不用擔心多線程訪問帶給我們的心智負擔了~)
breakBarrier
breakBarrier將打破計數器的限制,不管當前到達barrier的線程夠不夠,直接讓 當前已到達barrier 的線程們通過barrier。
private void breakBarrier() {
// 標記這一代是broken的
generation.broken = true;
// 恢復count值
count = parties;
// 喚醒條件隊列上的這一代線程(數量可能不夠parties個)
trip.signalAll();
}
這和nextGeneration
乾的事情幾乎一樣,除了它沒有去newGeneration
對象,只是標記當前Generation
對象是broken的。重點在於,它不會開啓新的一代。
同理,breakBarrier
的全程都是持有鎖的。
reset
reset會將CyclicBarrier恢復成初始狀態,裏面直接調用了上面的兩個方法。從下面可見,nextGeneration
和breakBarrier
全稱都是持有鎖的,不過這二者乾的事情有點重複,不過這樣重複乾的事情是不要緊的。
public void reset() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
breakBarrier(); // break the current generation
nextGeneration(); // start a new generation
} finally {
lock.unlock();
}
}
如果有線程在await()
的過程中發現自己這一代是broken的,則會拋出BrokenBarrierException
異常。
await
現在來看CyclicBarrier最重要的部分await
,它集齊了兩個功能:
- 計數器減一
- 阻塞等待,直到線程到齊(tripped)、BrokenBarrier(broken)、中斷(interrupted)、超時(timed out)。
await
方法有兩個重載版本,普通版本和超時版本,它們本質都調用同一個函數dowait
。
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // 從dowait(false, 0L)的邏輯來說是不可能拋出TimeoutException的,因爲參數爲false
}
}
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException {
return dowait(true, unit.toNanos(timeout));
}
從兩個函數拋出的異常就可以知道,這二者退出await的形式可能是下面這四種:
正常退出await (即有parties個線程到達了barrier) |
因中斷而退出 (拋出InterruptedException) |
因BrokenBarrier而退出 (拋出BrokenBarrierException) |
因超時而退出 (拋出TimeoutException) |
|
---|---|---|---|---|
await() |
✓ | ✓ | ✓ | - |
await(long timeout, TimeUnit unit) |
✓ | ✓ | ✓ | ✓ |
注意還有返回值,代表還需要多少個線程到達barrier,比如第一個線程調用await
後,返回的是parties-1;最後一次線程返回0。
private int dowait(boolean timed, long nanos)//timed參數爲true,纔可能拋出TimeoutException
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
lock.lock();//整個執行過程必須先獲得鎖
try {
// 這個Generation局部變量保存起來很重要,因爲CyclicBarrier的generation成員在通過barrier後會替換爲新對象
// 獲得的Generation對象肯定是當前線程應該在的“代”
final Generation g = generation;
if (g.broken) //如果當前線程在調用dowait前,當代generation就是broken的
//之所以我敢肯定說是調用dowait前,是因爲所有操作的前提都是要先獲得鎖
throw new BrokenBarrierException();
//如果當前線程在調用dowait前,被中斷了
//被中斷說明當前線程取消掉了,而CyclicBarrier本着“一個都不少”的原則,就不會讓同一代的其他線程苟活下去
//所以就要先breakBarrier,再拋出InterruptedException異常。
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
//到達barrier時,將count減一的值,這個值作爲正常返回時的返回值
int index = --count;
//如果當前線程是通過barrier所需要的最後一個線程,即之前已經到達了parties-1個線程
if (index == 0) { // tripped
boolean ranAction = false;//用來標記command.run()執行過程中是否出錯
try {
final Runnable command = barrierCommand;
//當代中最後一個線程,負責執行command.run()
if (command != null)
//說明通過barrier前,有事情需要做
command.run();
ranAction = true;//標記command.run()執行過程中沒有出錯
nextGeneration();//喚醒條件隊列的所有線程,開啓下一代
return 0;
} finally {
if (!ranAction)//command.run()執行過程中出錯了,需要執行breakBarrier
breakBarrier();
}
}
//執行到這裏,說明當前線程肯定不是當代中的最後一個線程(即index不爲0)
//如果最後一個線程執行command.run()沒出錯,將return 0返回
//如果最後一個線程執行command.run()出錯了,執行finally塊後,將返回上層函數並拋出異常
// 循環不會結束,直到tripped, broken, interrupted, or timed out
for (;;) {
try {
// 兩種Condition的await都會阻塞,
// 如果是普通版本調用的dowait,那麼調用Condition的await
if (!timed)
trip.await();
// 如果是超時版本調用的dowait,那麼調用Condition的awaitNanos
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);//返回值是deadline減去當前時間的差值
} catch (InterruptedException ie) {
//執行到這裏,說明上面兩種Condition的await,被中斷而喚醒
//如果局部變量和成員變量是同一個對象,且當前不是broken的,則先breakBarrier
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
//執行到這裏有兩種情況:
//1. g != generation,說明成員變量已經更新了,此時再去breakBarrier
// 只能影響到新一代的broken成員,所以不能執行breakBarrier
//2. 雖然g == generation,但已經是broken的了。既然barrier已經被打破
// 那就不用再執行breakBarrier了
// 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();
}
}
//只要自己這一代是broken的,就拋出異常。只有breakBarrier函數會改變broken,這裏給出執行breakBarrier的場景:
//1. 當代的其他線程調用CyclicBarrier#await之前就已經被中斷了(強調“其他”是因爲,如果是這個原因,不會執行到這裏)
//2. 當代的其他線程調用Condition#await阻塞後被中斷(上面的catch塊)
//3. 最後到達barrier的線程執行barrierCommand時出錯了
//4. reset方法被執行,這可以是任意線程
if (g.broken)
throw new BrokenBarrierException();
// 執行到這裏,說明當代g不是broken的,且最後一個線程已經到達barrier
//(最後一個到達了,會執行nextGeneration更新generation成員的)
if (g != generation)
//此時纔可以正常返回
return index;
//執行到這裏,說明當代g不是broken的,CyclicBarrier的代也沒有更新呢
//說明最後一個線程還沒來,自己就超時了
//如果發現已經超時,就打破barrier,拋出異常
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();//就算拋出異常,拋出之前也會釋放鎖的
}
}
主要細節我已經在註釋中說明了。
簡單總結下:
- 一般情況下,每個線程開始都令
count
成員減一,最終返回也是減一後的值。 - 在每一代中,前parties-1個線程到達barrier時,會執行
Condition#await
(指兩個版本)而阻塞。 - 在每一代中,第parties個線程到達barrier時,它負責執行
barrierCommand
,不管是否出錯,都將返回上層代碼(可能以拋出異常的形式)。 - 保存的局部變量
g
一定是當前線程所屬的“代”,而CyclicBarrier.generation
成員變量則保存當前CyclicBarrier進行到第幾“代”了。這兩個變量很重要。
從問題分析深入理解CyclicBarrier
就算理解了dowait
的每行代碼的邏輯,可能也不瞭解整個CyclicBarrier
的協作過程,所以我們通過以下幾個問題的解答來深入理解CyclicBarrier
。
有幾種線程在執行?
從宏觀的角度來看,只有兩種:
- 執行
dowait
的線程。這些線程想要正常通過barrier。 - 執行
reset
的線程。讓當代的線程全部作廢(拋出BrokenBarrierException),開啓下一代。
從Generation的狀態來看:
- 執行
breakBarrier
的線程。它只會讓當代Generation變成broken,但不會開啓下一代。注意,一旦broken了,就沒法再變回去了,只有靠nextGeneration
生成新的Generation
了。 - 執行
nextGeneration
的線程。生成新的Generation
,新生成的Generation
狀態就不是broken的了。
從線程的阻塞喚醒狀態來看:
- 執行
Condition#await
的線程。這類線程不是每一代中到達barrier的最後一個線程,所以都會調用Condition#await
而阻塞。 - 執行
Condition#signalAll
的線程。可以是任意線程。- 如果是每一代中到達barrier的最後一個線程,不管執行
barrierCommand
是否出錯,最終都會執行到Condition#signalAll
。 - 如果不是每一代中到達barrier的最後一個線程,也可能因爲中斷或超時,而去執行
breakBarrier
,進而執行到Condition#signalAll
。
- 如果是每一代中到達barrier的最後一個線程,不管執行
一代線程們通過barrier的完整流程是什麼?
假設每次通過barrier需要n個線程。假設執行barrierCommand
不出錯:
- 前n-1個線程,都會將count減1,然後調用
Condition#await
(這裏指兩個版本)阻塞。前n-1個線程的node此時都處於condition queue
上。 - 第n個線程,將count減到0,假設執行
barrierCommand
不出錯,將調用nextGeneration
裏的Condition#signalAll
,將前n-1個線程的node轉移到sync queue
上。注意nextGeneration
會更新generation
成員,也就是說前n-1個線程保存的局部變量g
已經和成員變量不相等了。 - 第n個線程
return 0
之前,將執行finally
塊裏的lock.unlock()
,然後喚醒第1個線程。 - 第1個線程獲得鎖後,從
Condition#await
中退出後,因爲if (g != generation) return index;
而退出,退出前它將執行finally
塊裏的lock.unlock()
,然後喚醒第2個線程。 - 以此類推,最終前n-1個線程都將從
CyclicBarrier#await
中正常退出。
以上過程,從使用者的角度來看,就是第n個線程到達barrier後,所有線程都開始重新工作了。
假設執行barrierCommand
出錯:
- 前n-1個線程,都會將count減1,然後調用
Condition#await
(這裏指兩個版本)阻塞。前n-1個線程的node此時都處於condition queue
上。 - 第n個線程,將count減到0,如果執行
barrierCommand
出錯,將調用breakBarrier
裏的Condition#signalAll
,將前n-1個線程的node轉移到sync queue
上。注意,breakBarrier
不會更新generation
成員,但會把generation
成員變成broken的。 - 第n個線程在拋出“執行
barrierCommand
出錯”的異常前,將執行finally
塊裏的lock.unlock()
,然後喚醒第1個線程。 - 第1個線程獲得鎖後,從
Condition#await
中退出後,因爲if (g.broken) throw new BrokenBarrierException();
而拋異常,拋異常前它將執行finally
塊裏的lock.unlock()
,然後喚醒第2個線程。 - 以此類推,最終前n-1個線程都將從
CyclicBarrier#await
中收穫一個BrokenBarrierException
。
以上過程,從使用者的角度來看,似乎到達了所有線程要麼都做(每個線程都從CyclicBarrier#await
正常返回),要麼都不做(每個線程都從CyclicBarrier#await
拋出了異常)的目的。
前n-1個線程發生中斷或超時的流程是什麼?
假設第n個線程還沒有到來,前n-1個線程中的任意線程發生中斷或超時。
先分析中斷:
- 如果某個線程發生了中斷,它從
Condition#await
處會拋出中斷異常。- 此時,當前線程的node已經轉移到了
sync queue
(因爲Condition#await
裏的checkInterruptWhileWaiting
),然後執行Condition#await
的acquireQueued
進行阻塞式的搶鎖,退出Condition#await
之前根據interruptMode
而拋出了異常。具體請看Condition接口的實現。
- 此時,當前線程的node已經轉移到了
- 拋出中斷異常被catch住,進入
if (g == generation && ! g.broken)
分支,執行breakBarrier
裏的Condition#signalAll
,喚醒在條件隊列上的所有線程。 - 其他線程被喚醒後,因爲
if (g.broken) throw new BrokenBarrierException();
拋出BrokenBarrier異常。
再分析超時:
- 如果某個線程發生了超時,它從
Condition#awaitNanos
處正常返回但返回值是一個<=的數。 - 接下來會執行到
if (timed && nanos <= 0L)
分支,因爲前面的分支都沒有進入,執行breakBarrier
裏的Condition#signalAll
,喚醒在條件隊列上的所有線程。 - 其他線程被喚醒後,因爲
if (g.broken) throw new BrokenBarrierException();
拋出BrokenBarrier異常。
從上述過程可見,在當代還沒有被breakBarrier時,第一個出問題的線程會報告問題原因(中斷異常或超時異常),而當代的其他線程則只會報告BrokenBarrierException
的原因了。
還有一種情況,當代的其他線程會帶有更多信息。考慮上面兩種過程,第一個出問題的線程還沒有執行到Condition#signalAll
,當代其他某一個線程被中斷了(因爲第一個出問題線程還沒釋放鎖,所以不會從Condition#await
處退出,但Condition#await
會記錄下來這個比signal流程來得更早的中斷),等到這某一個線程從Condition#await
處搶到鎖返回時,肯定會拋出中斷異常。然後中斷異常被catch,執行if (g == generation && ! g.broken)
的else分支,再自我中斷一下,最後才拋出BrokenBarrierException
。此時,線程拋出BrokenBarrierException
的同時還帶有中斷狀態。
第n個線程在執行nextGeneration()之前,前面的線程中斷或超時會怎麼樣?
簡單的說,不會怎麼樣。因爲第n個線程在執行nextGeneration()
之前,肯定還沒有釋放鎖。而前面的線程因爲獲得不到鎖,還是會在Condition#await
裏的acquireQueued
繼續阻塞。
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
如上代碼,假設第n個線程執行barrierCommand
不出錯,在執行nextGeneration()
之前還需要一段時間。
既然Condition#await
裏的acquireQueued
只有在第n個線程執行nextGeneration()
然後釋放鎖後才能搶到鎖,所以“前面的線程中斷或超時”的具體結果,就交給下一個問題分析。
第n個線程在執行nextGeneration()後,前面的線程中斷或超時會怎麼樣?
第n個線程都執行完了nextGeneration()
,說明執行barrierCommand
沒有出錯,此時generation
成員已經被更新,也就是說,前面的線程在判斷g == generation
肯定不成立。
更重要的是,一旦第n個線程都執行完了nextGeneration()
,舊generation
的broken
屬性就不可能再被改變了,因爲breakBarrier
只能broken當前的generation
成員。
而既然第n個線程能執行nextGeneration()
,那說明沒有進入之前的if (g.broken) throw new BrokenBarrierException();
分支,第n個線程執行時,那個舊generation
的broken
屬性肯定爲false。
假如前面的線程被中斷:
- 當前線程從
Condition#await
處喚醒,catch處中斷異常,由於g != generation
而進入if (g == generation && ! g.broken)
的else分支,即自我中斷一下。 - 局部變量
g
保存着當前線程的代,根據上面分析它肯定不是broken的。所以不會if (g.broken) throw new BrokenBarrierException();
。 - 因爲
if (g != generation)
成立,而正常返回。
前面的線程超時的流程同理,只不過第一步不會進入catch處中,然後直接第二步。
//執行到這裏,說明g不是broken的
if (g != generation)
return index;
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
這也解釋了爲什麼dowait
的最後兩個判斷是這個順序。只有超時事件在第n個線程到來之前,即當前線程所屬的代和CyclicBarrier的generation
成員是同一個對象時,才需要去響應超時,拋出超時異常。
現在回答這個問題:對於每一代來說,第n個線程在執行nextGeneration()
後,前面的線程中斷或超時不會響應而拋出異常,因爲第n個線程已經順利通過了barrier,前n-1個線程遲早也會順利通過的。
g == generation成立,意味着什麼?
意味着當前這一代,第n個線程還沒有到達barrier,如果它到達了,就會執行nextGeneration()的,這個條件也就不會成立。
CyclicBarrier使用的非公平鎖有什麼影響?
從CyclicBarrier的構造器中可見,使用的是ReentrantLock
的非公平鎖。
考慮這種場景:
- 第n個線程執行完
nextGeneration()
後,執行lock.unlock()
釋放鎖並喚醒第1個線程。 - 第1個線程從
Condition#await
裏的LockSupport.park(this)
處被喚醒,然後通過acquireQueued(node, savedState)
開始搶鎖。 - 第n+1個線程(這屬於第二代)執行
CyclicBarrier#await
裏的lock.lock()
,也開始搶鎖。 - 本來第1個線程的node處於
sync queue
中的第一個位置(head後繼),但由於是非公平搶鎖,也有可能第n+1個線程搶到鎖。
雖然第n+1個線程搶到鎖後,也是調用Condition#await
後馬上釋放鎖,但如果這種非公平方式多次搶佔成功,可能會造成第一代的線程們從CyclicBarrier#await
處返回得比較慢。
第n+1個線程獲得到的Generation局部變量,會不會是第一代的?
如果第n個線程順利通過了barrier(執行barrierCommand
沒出錯),那麼不會,肯定是第二代Generation。
如果第n個線程執行barrierCommand
出錯,那麼第n+1個線程獲得的是第一代Generation,但第n+1個線程馬上會拋出BrokenBarrierException
,所以不用考慮。
條件隊列上的node的線程,肯定是同一“代”的嗎?
是的。不管是第n個線程執行了nextGeneration
,還是reset
執行的nextGeneration
,都會先將用signalAll
把條件隊列清空,再更新generation
成員。此後,第n+1個線程調用CyclicBarrier#await
獲得的局部變量肯定是另一個Generation
成員了。
同步隊列上的node的線程,肯定是同一“代”的嗎?
不是。比如第2n+1個線程調用CyclicBarrier#await
獲得的局部變量已經是第三代Generation
了,但可能sync queue
的head後繼還是第一代的線程node。
如果generation成員已經broken,該怎麼辦?
因爲沒有方法能讓generation
成員的broken
屬性從true變回false,所以只有靠reset
方法來更新generation
成員,不然這個CyclicBarrier就一直都不可用了(線程一調用await()
就馬上拋出BrokenBarrierException)。
所以我們在發現有BrokenBarrierException
拋出後,應該去調用reset
方法來重置CyclicBarrier,反正這一代的線程們遲早也會拋出BrokenBarrierException
。
調用await(0, TimeUnit.SECONDS)可能導致TimeoutException?
分兩種情況:
如果是前n-1個線程的任意線程通過調用await(0, TimeUnit.SECONDS)
到達的barrier,流程如下:
- 執行到for循環,由於
timed
爲true,且nanos
爲0,所以根本不會阻塞。 - 假設前面兩個分支都不會進入,所以最終進入
if (timed && nanos <= 0L)
分支,而拋出TimeoutException
。 - 這還導致它之前的其他線程被喚醒後,會拋出
BrokenBarrierException
。
這從語義上講得通,前n-1個線程要求等待0單位的時間,時間到了(因爲是0,所以實際上根本沒等),發現第n個線程還沒有到達barrier(等價於g == generation
,因爲如果到達了會nextGeneration
從而更新generation
成員的),所以就拋出TimeoutException
。
如果是第n個線程通過調用await(0, TimeUnit.SECONDS)
到達的barrier,那麼不會發生上面這種異常情況,因爲直接進入了if (index == 0)
分支。
總結
- CyclicBarrier類似於CountDownLatch但又不同,在CyclicBarrier裏,每個線程既要CountDown減小計數器,也要阻塞等待直到count爲0(即等待線程到齊)。
- CyclicBarrier基於獨佔鎖、同步隊列、條件隊列而實現。
- CyclicBarrier可以重複使用,每有parties個線程到達barrier,開啓新的一代。然後舊的一代線程就會相繼從
CyclicBarrier#await
退出。 - CyclicBarrier實現了
all-or-none breakage model
的原則,同一代線程要麼都正常返回CyclicBarrier#await
,要麼都從CyclicBarrier#await
拋出異常。如果一個線程因爲中斷、超時或執行barrierCommand
而出錯,將拋出用於解釋原因的異常,那麼就broken掉CyclicBarrier,同一代的其他線程之後會拋出BrokenBarrierException
。