JUC框架 CyclicBarrier源碼解析 JDK8

前言

上一篇文章CountDownLatch源碼解析我們學習了CountDownLatch,接下來我們來分析一下和它用法很類似的CyclicBarrier。

JUC框架 系列文章目錄

與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恢復成初始狀態,裏面直接調用了上面的兩個方法。從下面可見,nextGenerationbreakBarrier全稱都是持有鎖的,不過這二者乾的事情有點重複,不過這樣重複乾的事情是不要緊的。

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,它集齊了兩個功能:

  1. 計數器減一
  2. 阻塞等待,直到線程到齊(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需要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#awaitacquireQueued進行阻塞式的搶鎖,退出Condition#await之前根據interruptMode而拋出了異常。具體請看Condition接口的實現
  • 拋出中斷異常被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(),舊generationbroken屬性就不可能再被改變了,因爲breakBarrier只能broken當前的generation成員

而既然第n個線程能執行nextGeneration(),那說明沒有進入之前的if (g.broken) throw new BrokenBarrierException();分支,第n個線程執行時,那個舊generationbroken屬性肯定爲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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章