解析阿里一面CyclicBarrier和CountDownLatch的區(qū)別
引言
前面一篇文章我們《Java線程并發(fā)工具類CountDownLatch原理及用法》它有一個缺點,就是它的計數(shù)器只能夠使用一次,也就是說當(dāng)計數(shù)器(state
)減到為 0
的時候,如果 再有線程調(diào)用去 await
() 方法,該線程會直接通過,不會再起到等待其他線程執(zhí)行結(jié)果起到同步的作用。為了解決這個問題CyclicBarrier
就應(yīng)運(yùn)而生了。
什么是CyclicBarrier
CyclicBarrier
是什么?把它拆開來翻譯就是循環(huán)(Cycle
)和屏障(Barrier
)
它的主要作用其實和CountDownLanch
差不多,都是讓一組線程到達(dá)一個屏障時被阻塞,直到最后一個線程到達(dá)屏障時,屏障會被打開,所有被屏障阻塞的線程才會繼續(xù)執(zhí)行,不過它是可以循環(huán)執(zhí)行的,這是它與CountDownLanch
最大的不同。CountDownLanch
是只有當(dāng)最后一個線程把計數(shù)器置為0
的時候,其他阻塞的線程才會繼續(xù)執(zhí)行。學(xué)習(xí)CyclicBarrier
之前建議先去看看這幾篇文章:
《Java高并發(fā)編程基礎(chǔ)三大利器之Semaphore》
《Java高并發(fā)編程基礎(chǔ)三大利器之CountDownLatch》
如何使用
我們首先先來看下關(guān)于使用CyclicBarrier
的一個demo
:比如游戲中有個關(guān)卡的時候,每次進(jìn)入下一關(guān)的時候都需要進(jìn)行加載一些地圖、特效背景音樂什么的只有全部加載完了才能夠進(jìn)行游戲:
public class CyclicBarrierExample { static class PreTaskThread implements Runnable { private String task; private CyclicBarrier cyclicBarrier; public PreTaskThread(String task, CyclicBarrier cyclicBarrier) { this.task = task; this.cyclicBarrier = cyclicBarrier; } @Override public void run() { for (int i = 0; i < 4; i++) { Random random = new Random(); try { Thread.sleep(random.nextInt(1000)); System.out.println(String.format("關(guān)卡 %d 的任務(wù) %s 完成", i, task)); cyclicBarrier.await(); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } } } public static void main(String[] args) { CyclicBarrier cyclicBarrier = new CyclicBarrier(3, () -> { System.out.println("本關(guān)卡所有的前置任務(wù)完成,開始游戲... ..."); }); new Thread(new PreTaskThread("加載地圖數(shù)據(jù)", cyclicBarrier)).start(); new Thread(new PreTaskThread("加載人物模型", cyclicBarrier)).start(); new Thread(new PreTaskThread("加載背景音樂", cyclicBarrier)).start(); } } }
輸出結(jié)果如下:
我們可以看到每次游戲開始都會等當(dāng)前關(guān)卡把游戲的人物模型,地圖數(shù)據(jù)、背景音樂加載完成后才會開始進(jìn)行游戲。并且還是可以循環(huán)控制的。
源碼分析
結(jié)構(gòu)組成
/** 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();
- lock:用于保護(hù)屏障入口的鎖
- trip :達(dá)到屏障并且不能放行的線程在trip條件變量上等待
- parties :柵欄開啟需要的到達(dá)線程總數(shù)barrierCommand:最后一個線程到達(dá)屏障后執(zhí)行的回調(diào)任務(wù)
- generation:這是一個內(nèi)部類,通過它實現(xiàn)
CyclicBarrier
重復(fù)利用,每當(dāng)await
達(dá)到最大次數(shù)的時候,就會重新new
一個,表示進(jìn)入了下一個輪回。里面只有一個boolean
型屬性,用來表示當(dāng)前輪回是否有線程中斷。
主要方法
await
方法
public int await() throws InterruptedException, BrokenBarrierException { try { return dowait(false, 0L); } catch (TimeoutException toe) { throw new Error(toe); // cannot happen } } /** * Main barrier code, covering the various policies. */ private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException { final ReentrantLock lock = this.lock; lock.lock(); try { //獲取barrier當(dāng)前的 “代”也就是當(dāng)前循環(huán) final Generation g = generation; if (g.broken) throw new BrokenBarrierException(); if (Thread.interrupted()) { breakBarrier(); throw new InterruptedException(); } // 每來一個線程調(diào)用await方法都會進(jìn)行減1 int index = --count; if (index == 0) { // tripped boolean ranAction = false; try { final Runnable command = barrierCommand; // new CyclicBarrier 傳入 的barrierCommand, command.run()這個方法是同步的,如果耗時比較多的話,是否執(zhí)行的時候需要考慮下是否異步來執(zhí)行。 if (command != null) command.run(); ranAction = true; // 這個方法1. 喚醒所有阻塞的線程,2. 重置下count(count 每來一個線程都會進(jìn)行減1)和generation,以便于下次循環(huán)。 nextGeneration(); return 0; } finally { if (!ranAction) breakBarrier(); } } // loop until tripped, broken, interrupted, or timed out for (;;) { try { // 進(jìn)入if條件,說明是不帶超時的await if (!timed) // 當(dāng)前線程會釋放掉lock,然后進(jìn)入到trip條件隊列的尾部,然后掛起自己,等待被喚醒。 trip.await(); else if (nanos > 0L) //說明當(dāng)前線程調(diào)用await方法時 是指定了 超時時間的! nanos = trip.awaitNanos(nanos); } catch (InterruptedException ie) { //Node節(jié)點在 條件隊列內(nèi) 時 收到中斷信號時 會拋出中斷異常! //g == generation 成立,說明當(dāng)前代并沒有變化。 //! g.broken 當(dāng)前代如果沒有被打破,那么當(dāng)前線程就去打破,并且拋出異常.. 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. //執(zhí)行到else有幾種情況? //1.代發(fā)生了變化,這個時候就不需要拋出中斷異常了,因為 代已經(jīng)更新了,這里喚醒后就走正常邏輯了..只不過設(shè)置下 中斷標(biāo)記。 //2.代沒有發(fā)生變化,但是代被打破了,此時也不用返回中斷異常,執(zhí)行到下面的時候會拋出 brokenBarrier異常。也記錄下中斷標(biāo)記位。 Thread.currentThread().interrupt(); } } //喚醒后,執(zhí)行到這里,有幾種情況? //1.正常情況,當(dāng)前barrier開啟了新的一代(trip.signalAll()) //2.當(dāng)前Generation被打破,此時也會喚醒所有在trip上掛起的線程 //3.當(dāng)前線程trip中等待超時,然后主動轉(zhuǎn)移到 阻塞隊列 然后獲取到鎖 喚醒。 if (g.broken) throw new BrokenBarrierException(); //喚醒后,執(zhí)行到這里,有幾種情況? //1.正常情況,當(dāng)前barrier開啟了新的一代(trip.signalAll()) //2.當(dāng)前線程trip中等待超時,然后主動轉(zhuǎn)移到 阻塞隊列 然后獲取到鎖 喚醒。 if (g != generation) return index; //喚醒后,執(zhí)行到這里,有幾種情況? //.當(dāng)前線程trip中等待超時,然后主動轉(zhuǎn)移到 阻塞隊列 然后獲取到鎖 喚醒。 if (timed && nanos <= 0L) { breakBarrier(); throw new TimeoutException(); } } } finally { lock.unlock(); } }
小結(jié)
到了這里我們是不是可以知道為啥CyclicBarrier
可以進(jìn)行循環(huán)計數(shù)?
CyclicBarrier
采用一個內(nèi)部類Generation
來維護(hù)當(dāng)前循環(huán),每一個await
方法都會存儲當(dāng)前的generation
,獲取到相同generation
對象的屬于同一組,每當(dāng)count
的次數(shù)耗盡就會重新new
一個Generation
并且重新設(shè)置count
的值為parties
,表示進(jìn)入下一次新的循環(huán)。
從這個await
方法我們是不是可以知道只要有一個線程被中斷了,當(dāng)代的 generation
的broken
就會被設(shè)置為true
,所以會導(dǎo)致其他的線程也會被拋出BrokenBarrierException
。相當(dāng)于一個失敗其他也必須失敗,感覺有“強(qiáng)一致性“的味道。
總結(jié)
CountDownLanch
是為計數(shù)器是設(shè)置一個值,當(dāng)多次執(zhí)行countdown
后,計數(shù)器減為0
的時候所有線程被喚醒,然后CountDownLanch
失效,只能夠使用一次。
CyclicBarrier
是當(dāng)count
為0
時同樣喚醒全部線程,同時會重新設(shè)置count
為parties
,重新new
一個generation
來實現(xiàn)重復(fù)利用。
結(jié)束
- 由于自己才疏學(xué)淺,難免會有紕漏,假如你發(fā)現(xiàn)了錯誤的地方,還望留言給我指出來,我會對其加以修正。
- 如果你覺得文章還不錯,你的轉(zhuǎn)發(fā)、分享、贊賞、點贊、留言就是對我最大的鼓勵。感
- 謝您的閱讀,十分歡迎并感謝您的關(guān)注。
巨人的肩膀摘蘋果
https://javajr.cn/
http://www.360doc.com/content/20/0812/08/55930996_929792021.shtml
https://www.cnblogs.com/xxyyy/p/12958160.html
到此這篇關(guān)于阿里一面CyclicBarrier和CountDownLatch的區(qū)別是啥的文章就介紹到這了,更多相關(guān)CyclicBarrier和CountDownLatch的區(qū)別內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實現(xiàn)PDF轉(zhuǎn)HTML/Word/Excel/PPT/PNG的示例代碼
這篇文章主要為大家介紹了如何利用Java語言是PDF轉(zhuǎn)HTML、Word、Excel、PPT和PNG功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2022-05-05SpringBoot Redis緩存數(shù)據(jù)實現(xiàn)解析
這篇文章主要介紹了SpringBoot Redis緩存數(shù)據(jù)實現(xiàn)解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-01-01SpringAOP中的切點表達(dá)式Pointcut詳解
這篇文章主要介紹了SpringAOP中的切點表達(dá)式Pointcut詳解,Spring?的?AOP?中的一個核心概念是切點(Pointcut),切點表達(dá)式定義通知(Advice)執(zhí)行的范圍,需要的朋友可以參考下2023-08-08MyBatisPlus 一對多、多對一、多對多的完美解決方案
這篇文章主要介紹了MyBatisPlus 一對多、多對一、多對多的完美解決方案,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-11-11