高並發之ReentrantLock、CountDownLatch、CyclicBarrier
本系列研究總結高並發下的幾種同步鎖的使用以及之間的區別,分別是:ReentrantLock、CountDownLatch、CyclicBarrier、Phaser、ReadWriteLock、StampedLock、Semaphore、Exchanger、LockSupport。由於博客園對博客字數的要求限制,會分為三個系列:
高並發之ReentrantLock、CountDownLatch、CyclicBarrier
高並發之Phaser、ReadWriteLock、StampedLock
高並發之Semaphore、Exchanger、LockSupport
ReentrantLock
ReentrantLock重入鎖,是實現Lock接口的一個類,也是在實際編程中使用頻率很高的一個鎖,支持重入性,表示能夠對共享資源能夠重複加鎖,即當前線程獲取該鎖再次獲取不會被阻塞。在java關鍵字synchronized隱式支持重入性(關於synchronized可以看這篇文章),synchronized通過獲取自增,釋放自減的方式實現重入。與此同時,ReentrantLock還支持公平鎖和非公平鎖兩種方式。那麼,要想完完全全的弄懂ReentrantLock的話,主要也就是ReentrantLock同步語義的學習:1. 重入性的實現原理;2. 公平鎖和非公平鎖。
重入性的實現原理
要想支持重入性,就要解決兩個問題:
1. 在線程獲取鎖的時候,如果已經獲取鎖的線程是當前線程的話則直接再次獲取成功;
2. 由於鎖會被獲取n次,那麼只有鎖在被釋放同樣的n次之後,該鎖才算是完全釋放成功。
我們知道,同步組件主要是通過重寫AQS的幾個protected方法來表達自己的同步語義。針對第一個問題,我們來看看ReentrantLock是怎樣實現的,以非公平鎖為例,判斷當前線程能否獲得鎖為例,核心方法為nonfairTryAcquire:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//1. 如果該鎖未被任何線程佔有,該鎖能被當前線程獲取
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//2.若被佔有,檢查佔有線程是否是當前線程
else if (current == getExclusiveOwnerThread()) {
// 3. 再次獲取,計數加一
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
為了支持重入性,在第二步增加了處理邏輯,如果該鎖已經被線程所佔有了,會繼續檢查佔有線程是否為當前線程,如果是的話,同步狀態加1返回true,表示可以再次獲取成功。每次重新獲取都會對同步狀態進行加一的操作,那麼釋放的時候處理思路是怎樣的了?(依然還是以非公平鎖為例)核心方法為tryRelease:
protected final boolean tryRelease(int releases) {
//1. 同步狀態減1
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
//2. 只有當同步狀態為0時,鎖成功被釋放,返回true
free = true;
setExclusiveOwnerThread(null);
}
// 3. 鎖未被完全釋放,返回false
setState(c);
return free;
}
需要注意的是,重入鎖的釋放必須得等到同步狀態為0時鎖才算成功釋放,否則鎖仍未釋放。如果鎖被獲取n次,釋放了n-1次,該鎖未完全釋放返回false,只有被釋放n次才算成功釋放,返回true。到現在我們可以理清ReentrantLock重入性的實現了,也就是理解了同步語義的第一條。
公平鎖與非公平鎖
ReentrantLock支持兩種鎖:公平鎖和非公平鎖。何謂公平性,是針對獲取鎖而言的,如果一個鎖是公平的,那麼鎖的獲取順序就應該符合請求上的絕對時間順序,滿足FIFO。ReentrantLock的構造方法無參時是構造非公平鎖,源碼為:
public ReentrantLock() {
sync = new NonfairSync();
}
另外還提供了另外一種方式,可傳入一個boolean值,true時為公平鎖,false時為非公平鎖,源碼為:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
在上面非公平鎖獲取時(nonfairTryAcquire方法)只是簡單的獲取了一下當前狀態做了一些邏輯處理,並沒有考慮到當前同步隊列中線程等待的情況。我們來看看公平鎖的處理邏輯是怎樣的,核心方法為:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
這段代碼的邏輯與nonfairTryAcquire基本上一致,唯一的不同在於增加了hasQueuedPredecessors的邏輯判斷,方法名就可知道該方法用來判斷當前節點在同步隊列中是否有前驅節點的判斷,如果有前驅節點說明有線程比當前線程更早的請求資源,根據公平性,當前線程請求資源失敗。如果當前節點沒有前驅節點的話,再才有做後面的邏輯判斷的必要性。公平鎖每次都是從同步隊列中的第一個節點獲取到鎖,而非公平性鎖則不一定,有可能剛釋放鎖的線程能再次獲取到鎖。
公平鎖與非公平鎖總結
- 公平鎖每次獲取到鎖為同步隊列中的第一個節點,保證請求資源時間上的絕對順序,而非公平鎖有可能剛釋放鎖的線程下次繼續獲取該鎖,則有可能導致其他線程永遠無法獲取到鎖,造成「飢餓」現象。
- 公平鎖為了保證時間上的絕對順序,需要頻繁的上下文切換,而非公平鎖會降低一定的上下文切換,降低性能開銷。因此,ReentrantLock默認選擇的是非公平鎖,則是為了減少一部分上下文切換,保證了系統更大的吞吐量。
CountDownLatch
CountDownLatch,這個詞語是由Count Down、Latch兩部分組成,意思是倒數門閂,為了形象記憶,可以理解為一個門上有很多個門閂,只有所有門閂都打開,也就是為0時才可以通過這個門。
創建對象時需要在構造CountDownLatch中傳入一個整數n,設置門閂的個數, 在這個整數「倒數」到0之前,線程需要等待在門口,而這個「倒數」過程則是由各個執行線程驅動的,每個線程執行完一個任務「倒數」一次(門閂減一)。總結來說,CountDownLatch的作用就是等待其他的線程都執行完任務,必要時可以對各個任務的執行結果進行匯總,然後線程才繼續往下執行。
CountDownLatch主要有兩個方法:countDown()和await()。countDown()方法用於使計數器減一,其一般是執行任務的線程調用,await()方法則使調用該方法的線程處於等待狀態,其一般是主線程調用。這裡需要注意的是,countDown()方法並沒有規定一個線程只能調用一次,當同一個線程調用多次countDown()方法時,每次都會使計數器減一;另外,await()方法也並沒有規定只能有一個線程執行該方法,如果多個線程同時執行await()方法,那麼這幾個線程都將處於等待狀態,並且以共享模式享有同一個鎖。
使用示例
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(5);
Service service = new Service(latch);
Runnable task = () -> service.exec();
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(task);
thread.start();
}
System.out.println("main thread await. ");
latch.await();
System.out.println("main thread finishes await. ");
}
}
其中的Service類如下:
public class Service {
private CountDownLatch latch;
//需要將countdownLatch傳遞進來
public Service(CountDownLatch latch) {
this.latch = latch;
}
public void exec() {
try {
System.out.println(Thread.currentThread().getName() + " execute task. ");
sleep(2);
System.out.println(Thread.currentThread().getName() + " finished task. ");
} finally {
//放在finally中是為了異常也能執行,不至於主線程死鎖!
latch.countDown();
}
}
private void sleep(int seconds) {
try {
TimeUnit.SECONDS.sleep(seconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
首先聲明了一個CountDownLatch對象,並且由主線程創建了5個線程,分別執行任務,在每個任務中,當前線程會休眠2秒。在啟動線程之後,主線程調用了CountDownLatch.await()方法,此時,主線程將在此處等待創建的5個線程執行完任務之後才繼續往下執行。
如下是執行結果:
main thread await.
Thread-1 execute task.
Thread-3 execute task.
Thread-0 execute task.
Thread-2 execute task.
Thread-4 execute task.
Thread-2 finished task.
Thread-3 finished task.
Thread-1 finished task.
Thread-0 finished task.
Thread-4 finished task.
main thread finishes await.
從輸出結果可以看出,主線程先啟動了五個線程,然後主線程進入等待狀態,當這五個線程都執行完任務之後主線程才結束了等待。上述代碼中需要注意的是,在執行任務的線程中,使用了try…finally結構,該結構可以保證創建的線程發生異常時CountDownLatch.countDown()方法也會執行,也就保證了主線程不會一直處於等待狀態。
適用場景
CountDownLatch非常適合於對任務進行拆分,使其並行執行,比如某個任務執行2s,其對數據的請求可以分為五個部分,那麼就可以將這個任務拆分為5個子任務,分別交由五個線程執行,執行完成之後再由主線程進行匯總,此時,總的執行時間將決定於執行最慢的任務,平均來看,還是大大減少了總的執行時間。
另外一種比較合適使用CountDownLatch的地方是使用某些外部鏈接請求數據的時候,比如圖片,因為我們使用的圖片服務只提供了獲取單個圖片的功能,而每次獲取圖片的時間不等,一般都需要1.5s~2s。當我們需要批量獲取圖片的時候,比如列表頁需要展示一系列的圖片,如果使用單個線程順序獲取,那麼等待時間將會極長,此時我們就可以使用CountDownLatch對獲取圖片的操作進行拆分,並行的獲取圖片,這樣也就縮短了總的獲取時間。
CountDownLatch是基於AbstractQueuedSynchronizer實現的,在AbstractQueuedSynchronizer中維護了一個volatile類型的整數state,volatile可以保證多線程環境下該變量的修改對每個線程都可見,並且由於該屬性為整型,因而對該變量的修改也是原子的。創建一個CountDownLatch對象時,所傳入的整數n就會賦值給state屬性,當countDown()方法調用時,該線程就會嘗試對state減一,而調用await()方法時,當前線程就會判斷state屬性是否為0,如果為0,則繼續往下執行,如果不為0,則使當前線程進入等待狀態,直到某個線程將state屬性置為0,其就會喚醒在await()方法中等待的線程。
countDown源碼
public void countDown() {
sync.releaseShared(1);
}
這裡sync也即一個繼承了AbstractQueuedSynchronizer的類實例,該類是CountDownLatch的一個內部類,
其聲明如下:
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState(); // 獲取當前state屬性的值
if (c == 0) // 如果state為0,則說明當前計數器已經計數完成,直接返回
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc)) // 使用CAS算法對state進行設置
return nextc == 0; // 設置成功後返回當前是否為最後一個設置state的線程
}
}
}
這裡tryReleaseShared(int)方法即對state屬性進行減一操作的代碼。可以看到,CAS也即compare and set的縮寫,jvm會保證該方法的原子性,其會比較state是否為c,如果是則將其設置為nextc(自減1),如果state不為c,則說明有另外的線程在getState()方法和compareAndSetState()方法調用之間對state進行了設置,當前線程也就沒有成功設置state屬性的值,其會進入下一次循環中,如此往複,直至其成功設置state屬性的值,即countDown()方法調用成功。
在countDown()方法中調用的sync.releaseShared(1)調用時實際還是調用的tryReleaseShared(int)方法,
releaseShared源碼
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
可以看到,在執行sync.releaseShared(1)方法時,其在調用tryReleaseShared(int)方法時會在無限for循環中設置state屬性的值,設置成功之後其會根據設置的返回值(此時state已經自減了一),即當前線程是否為將state屬性設置為0的線程,來判斷是否執行if塊中的代碼。doReleaseShared()方法主要作用是喚醒調用了await()方法的線程。需要注意的是,如果有多個線程調用了await()方法,這些線程都是以共享的方式等待在await()方法處的,試想,如果以獨佔的方式等待,那麼當計數器減少至零時,就只有一個線程會被喚醒執行await()之後的代碼,這顯然不符合邏輯。
doReleaseShared源碼
private void doReleaseShared() {
for (;;) {
Node h = head; // 記錄等待隊列中的頭結點的線程
if (h != null && h != tail) { // 頭結點不為空,且頭結點不等於尾節點
int ws = h.waitStatus;
if (ws == Node.SIGNAL) { // SIGNAL狀態表示當前節點正在等待被喚醒
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) // 清除當前節點的等待狀態
continue;
unparkSuccessor(h); // 喚醒當前節點的下一個節點
} else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
if (h == head) // 如果h還是指向頭結點,說明前面這段代碼執行過程中沒有其他線程對頭結點進行過處理
break;
}
}
在doReleaseShared()方法中(始終注意當前方法是最後一個執行countDown()方法的線程執行的),首先判斷頭結點不為空,且不為尾節點,說明等待隊列中有等待喚醒的線程,這裡需要說明的是,在等待隊列中,頭節點中並沒有保存正在等待的線程,其只是一個空的Node對象,真正等待的線程是從頭節點的下一個節點開始存放的,因而會有對頭結點是否等於尾節點的判斷。在判斷等待隊列中有正在等待的線程之後,其會清除頭結點的狀態信息,並且調用unparkSuccessor(Node)方法喚醒頭結點的下一個節點,使其繼續往下執行。
unparkSuccessor源碼
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0); // 清除當前節點的等待狀態
Node s = node.next;
if (s == null || s.waitStatus > 0) { // s的等待狀態大於0說明該節點中的線程已經被外部取消等待了
s = null;
// 從隊列尾部往前遍歷,找到最後一個處於等待狀態的節點,用s記錄下來
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread); // 喚醒離傳入節點最近的處於等待狀態的節點線程
}
可以看到,unparkSuccessor(Node)方法的作用是喚醒離傳入節點最近的一個處於等待狀態的線程,使其繼續往下執行。前面我們講到過,等待隊列中的線程可能有多個,而調用countDown()方法的線程只喚醒了一個處於等待狀態的線程,這裡剩下的等待線程是如何被喚醒的呢?其實這些線程是被當前喚醒的線程喚醒的。具體的我們可以看看await()方法的具體執行過程。
如下是await()方法的代碼:
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
await()方法實際還是調用了Sync對象的方法acquireSharedInterruptibly(int)方法,如下是該方法的具體實現:
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
可以看到acquireSharedInterruptibly(int)方法判斷當前線程是否需要以共享狀態獲取執行權限,這裡tryAcquireShared(int)方法是AbstractQueuedSynchronizer中的一個模板方法,其具體實現在前面的Sync類中,可以看到,其主要是判斷state是否為零,如果為零則返回1,表示當前線程不需要進行權限獲取,可直接執行後續代碼,返回-1則表示當前線程需要進行共享權限。具體的獲取執行權限的代碼在doAcquireSharedInterruptibly(int)方法中。
doAcquireSharedInterruptibly源碼
如下是該方法的具體實現:
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
final Node node = addWaiter(Node.SHARED); // 使用當前線程創建一個共享模式的節點
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor(); // 獲取當前節點的前一個節點
if (p == head) { // 判斷前一個節點是否為頭結點
int r = tryAcquireShared(arg); // 查看當前線程是否獲取到了執行權限
if (r >= 0) { // 大於0表示獲取了執行權限
setHeadAndPropagate(node, r); // 將當前節點設置為頭結點,並且喚醒後面處於等待狀態的節點
p.next = null; // help GC
failed = false;
return;
}
}
// 走到這一步說明沒有獲取到執行權限,就使當前線程進入「擱置」狀態
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
在doAcquireSharedInterruptibly(int)方法中,首先使用當前線程創建一個共享模式的節點。然後在一個for循環中判斷當前線程是否獲取到執行權限,如果有(r >= 0判斷)則將當前節點設置為頭節點,並且喚醒後續處於共享模式的節點;如果沒有,則對調用shouldParkAfterFailedAcquire(Node, Node)和parkAndCheckInterrupt()方法使當前線程處於「擱置」狀態,該「擱置」狀態是由操作系統進行的,這樣可以避免該線程無限循環而獲取不到執行權限,造成資源浪費,這裡也就是線程處於等待狀態的位置,也就是說當線程被阻塞的時候就是阻塞在這個位置。當有多個線程調用await()方法而進入等待狀態時,這幾個線程都將等待在此處。這裡回過頭來看前面將的countDown()方法,其會喚醒處於等待隊列中離頭節點最近的一個處於等待狀態的線程,也就是說該線程被喚醒之後會繼續從這個位置開始往下執行,此時執行到tryAcquireShared(int)方法時,發現r大於0(因為state已經被置為0了),該線程就會調用setHeadAndPropagate(Node, int)方法,並且退出當前循環,也就開始執行awat()方法之後的代碼。
setHeadAndPropagate源碼
這裡我們看看setHeadAndPropagate(Node, int)方法的具體實現:
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
setHead(node); // 將當前節點設置為頭節點
// 檢查喚醒過程是否需要往下傳遞,並且檢查頭結點的等待狀態
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared()) // 如果下一個節點是嘗試以共享狀態獲取獲取執行權限的節點,則將其喚醒
doReleaseShared();
}
}
setHeadAndPropagate(Node, int)方法主要作用是設置當前節點為頭結點,並且將喚醒工作往下傳遞,在傳遞的過程中,其會判斷被傳遞的節點是否是以共享模式嘗試獲取執行權限的,如果不是,則傳遞到該節點處為止(一般情況下,等待隊列中都只會都是處於共享模式或者處於獨佔模式的節點)。也就是說,頭結點會依次喚醒後續處於共享狀態的節點,這也就是共享鎖與獨佔鎖的實現方式。這裡doReleaseShared()方法也就是我們前面講到的會將離頭結點最近的一個處於等待狀態的節點喚醒的方法。
CyclicBarrier
CyclicBarrier也叫同步屏障,在JDK1.5被引入,可以讓一組線程達到一個屏障時被阻塞,直到最後一個線程達到屏障時,所以被阻塞的線程才能繼續執行。 CyclicBarrier好比一扇門,默認情況下關閉狀態,堵住了線程執行的道路,直到所有線程都就位,門才打開,讓所有線程一起通過。
使用方法
- 默認的構造方法是CyclicBarrier(int parties),其參數表示屏障攔截的線程數量,每個線程調用await方法告訴CyclicBarrier已經到達屏障位置,線程被阻塞。
- 另外一個構造方法CyclicBarrier(int parties, Runnable barrierAction),其中barrierAction任務會在所有線程到達屏障後執行。
應用場景及案例
可以想像飛虎隊(American Volunteer Group,AVG)要執行某項任務,需要等所有人到齊之後才能開始行動
class AVG implements Runnable {
private CyclicBarrier cyclicBarrier;
private String name;
public AVG(CyclicBarrier cyclicBarrier, String name) {
this.cyclicBarrier = cyclicBarrier;
this.name = name;
}
@Override
public void run() {
System.out.println(name + "就位");
try {
cyclicBarrier.await();
Random random =new Random();
double time = random.nextDouble() + 9;
System.out.println(name + ": "+ time);
} catch (Exception e) {
}
}
}
就位:
class Ready {
private CyclicBarrier cyclicBarrier = new CyclicBarrier(8);
public void start() {
List<Athlete> avgList = new ArrayList<>();
athleteList.add(new AVG(cyclicBarrier,"何永道"));
athleteList.add(new AVG(cyclicBarrier,"約翰·理乍得·羅西"));
athleteList.add(new AVG(cyclicBarrier,"查爾斯·龐德"));
athleteList.add(new AVG(cyclicBarrier,"羅伯特·尼爾"));
athleteList.add(new AVG(cyclicBarrier,"羅伯特·桑德爾"));
athleteList.add(new AVG(cyclicBarrier,"法蘭克·史基爾"));
athleteList.add(new AVG(cyclicBarrier,"約翰·牛柯克"));
athleteList.add(new AVG(cyclicBarrier,"大衛·李·希爾"));
Executor executor = Executors.newFixedThreadPool(8);
for (AVG avgmember : avgList) {
executor.execute(avgmember);
}
}
}
實現原理
CyclicBarrier實現主要基於ReentrantLock
public class CyclicBarrier {
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();
...省略後面代碼
}
其中Generation用來控制屏障的循環使用,如果generation.broken為true的話,說明這個屏障已經損壞,當某個線程await的時候,直接拋出異常。
await源碼實現
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
//利用ReentrantLock加鎖
lock.lock();
try {
final Generation g = generation;
//如果已經損壞,則拋出BrokenBarrierException異常
if (g.broken)
throw new BrokenBarrierException();
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
int index = --count;
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();
}
}
// 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();
}
}
if (g.broken)
throw new BrokenBarrierException();
if (g != generation)
return index;
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
解釋
- 每當線程執行await,內部變量count減1,如果count!= 0,說明有線程還未到屏障處,則在鎖條件變量trip上等待。
- 當count == 0時,說明所有線程都已經到屏障處,執行條件變量的signalAll方法喚醒等待的線程。
其中 nextGeneration方法可以實現屏障的循環使用:
重新生成Generation對象
恢復count值
與CountDownLatch的區別
- CountDownLatch 允許一個或多個線程等待一些特定的操作完成,而這些操作是在其它的線程中進行的,也就是說會出現 等待的線程 和 被等的線程 這樣分明的角色;
- CountDownLatch 構造函數中有一個 count 參數,表示有多少個線程需要被等待,對這個變量的修改是在其它線程中調用 countDown 方法,每一個不同的線程調用一次 countDown 方法就表示有一個被等待的線程到達,count 變為 0 時,latch(門閂)就會被打開,處於等待狀態的那些線程接着可以執行;
- CountDownLatch 是一次性使用的,也就是說latch門閂只能只用一次,一旦latch門閂被打開就不能再次關閉,將會一直保持打開狀態,因此 CountDownLatch 類也沒有為 count 變量提供 set 的方法;
舉例子:CD:司機在等人坐滿了才開車,阻塞主體是外部線程。 CB:人在等其他人來了再上車,阻塞主體是多個線程。