概念
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用於等待其他線程運行到柵欄位置