解析阿里一面CyclicBarrier和CountDownLatch的區(qū)別
引言
前面一篇文章我們《Java線程并發(fā)工具類CountDownLatch原理及用法》它有一個缺點(diǎn),就是它的計(jì)數(shù)器只能夠使用一次,也就是說當(dāng)計(jì)數(shù)器(state
)減到為 0
的時候,如果 再有線程調(diào)用去 await
() 方法,該線程會直接通過,不會再起到等待其他線程執(zhí)行結(jié)果起到同步的作用。為了解決這個問題CyclicBarrier
就應(yīng)運(yùn)而生了。
什么是CyclicBarrier
CyclicBarrier
是什么?把它拆開來翻譯就是循環(huán)(Cycle
)和屏障(Barrier
)
它的主要作用其實(shí)和CountDownLanch
差不多,都是讓一組線程到達(dá)一個屏障時被阻塞,直到最后一個線程到達(dá)屏障時,屏障會被打開,所有被屏障阻塞的線程才會繼續(xù)執(zhí)行,不過它是可以循環(huán)執(zhí)行的,這是它與CountDownLanch
最大的不同。CountDownLanch
是只有當(dāng)最后一個線程把計(jì)數(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)部類,通過它實(shí)現(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條件隊(duì)列的尾部,然后掛起自己,等待被喚醒。 trip.await(); else if (nanos > 0L) //說明當(dāng)前線程調(diào)用await方法時 是指定了 超時時間的! nanos = trip.awaitNanos(nanos); } catch (InterruptedException ie) { //Node節(jié)點(diǎn)在 條件隊(duì)列內(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ā)生了變化,這個時候就不需要拋出中斷異常了,因?yàn)?代已經(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)移到 阻塞隊(duì)列 然后獲取到鎖 喚醒。 if (g.broken) throw new BrokenBarrierException(); //喚醒后,執(zhí)行到這里,有幾種情況? //1.正常情況,當(dāng)前barrier開啟了新的一代(trip.signalAll()) //2.當(dāng)前線程trip中等待超時,然后主動轉(zhuǎn)移到 阻塞隊(duì)列 然后獲取到鎖 喚醒。 if (g != generation) return index; //喚醒后,執(zhí)行到這里,有幾種情況? //.當(dāng)前線程trip中等待超時,然后主動轉(zhuǎn)移到 阻塞隊(duì)列 然后獲取到鎖 喚醒。 if (timed && nanos <= 0L) { breakBarrier(); throw new TimeoutException(); } } } finally { lock.unlock(); } }
小結(jié)
到了這里我們是不是可以知道為啥CyclicBarrier
可以進(jìn)行循環(huán)計(jì)數(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
是為計(jì)數(shù)器是設(shè)置一個值,當(dāng)多次執(zhí)行countdown
后,計(jì)數(shù)器減為0
的時候所有線程被喚醒,然后CountDownLanch
失效,只能夠使用一次。
CyclicBarrier
是當(dāng)count
為0
時同樣喚醒全部線程,同時會重新設(shè)置count
為parties
,重新new
一個generation
來實(shí)現(xiàn)重復(fù)利用。
結(jié)束
- 由于自己才疏學(xué)淺,難免會有紕漏,假如你發(fā)現(xiàn)了錯誤的地方,還望留言給我指出來,我會對其加以修正。
- 如果你覺得文章還不錯,你的轉(zhuǎn)發(fā)、分享、贊賞、點(diǎn)贊、留言就是對我最大的鼓勵。感
- 謝您的閱讀,十分歡迎并感謝您的關(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實(shí)現(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ù)實(shí)現(xiàn)解析
這篇文章主要介紹了SpringBoot Redis緩存數(shù)據(jù)實(shí)現(xiàn)解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-01-01Netty實(shí)現(xiàn)自定義協(xié)議編解碼器
這篇文章主要為大家介紹了Netty實(shí)現(xiàn)自定義協(xié)議編解碼器示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02SpringAOP中的切點(diǎn)表達(dá)式Pointcut詳解
這篇文章主要介紹了SpringAOP中的切點(diǎn)表達(dá)式Pointcut詳解,Spring?的?AOP?中的一個核心概念是切點(diǎn)(Pointcut),切點(diǎn)表達(dá)式定義通知(Advice)執(zhí)行的范圍,需要的朋友可以參考下2023-08-08MyBatisPlus 一對多、多對一、多對多的完美解決方案
這篇文章主要介紹了MyBatisPlus 一對多、多對一、多對多的完美解決方案,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-11-11