CyclicBarrier源碼解析

一個多線程協(xié)同器品追,它可以讓一組線程相互等待,當(dāng)?shù)却臄?shù)量達(dá)到預(yù)設(shè)數(shù)量時這組線程通過等待繼續(xù)工作冯丙。說得形象點肉瓦,CyclicBarrier就好比汽車站滾動發(fā)車的模式,把客車看著CyclicBarrier银还,乘客看著是各個任務(wù)線程风宁,當(dāng)乘客到達(dá)客車時,需要等待另外的乘客蛹疯,當(dāng)乘客到齊后自動發(fā)車戒财,如果等待乘客超時了,則將乘客全部趕下車(司機太兇殘了)捺弦,然后重新安排依次上車(是否要上車由乘客自己決定)饮寞;每個上車的乘客都需要判斷自己是否是這輛車的最后一個乘客,如果不是列吼,則上車后立即開始睡覺幽崩,如果是最后一個,則他需要叫醒所有乘客寞钥。當(dāng)然客車站在創(chuàng)建這些客車的時候可能會做一些額外的事情慌申,例如所有乘客到齊后,司機給大伙一人發(fā)一瓶礦泉水,或者是其它的蹄溉,但是前提條件就是乘客到齊咨油。


8個線程協(xié)同的CyclicBarrier

圖中的CyclicBarrier需要等待8個線程到達(dá)后才會“發(fā)車”,目前已經(jīng)到達(dá)的線程有4個柒爵,還需要等待4個線程役电;線程上車的過程(也就是進(jìn)入await的過程)是要進(jìn)行排隊的,這里是通過ReentrantLock來實現(xiàn)的棉胀,上車后的睡眠是通過鎖的條件等待Condition來實現(xiàn)的法瑟。

首先看一下它的內(nèi)部整體結(jié)構(gòu)

public class CyclicBarrier {
    //一個標(biāo)識,標(biāo)識這一次的協(xié)同是否完成(正常完成唁奢,異常完成)
    private static class Generation {
        boolean broken = false;
    }

    //線程進(jìn)入條件等待時需要獲取鎖
    private final ReentrantLock lock = new ReentrantLock();
    //等待條件
    private final Condition trip = lock.newCondition();
    //每次需要協(xié)同的線程數(shù)(客車的準(zhǔn)載數(shù))
    private final int parties;
    //這組線程(parties)滿足協(xié)同條件后需要做的一件事情
    private final Runnable barrierCommand;
    //標(biāo)識實例霎挟,一個generation代表一次線程協(xié)同
    private Generation generation = new Generation();

    //還需要等待的線程數(shù)量(還未上車的乘客數(shù))
    private int count;

    //最后一個乘客上車后使用的工具,喚醒所有乘客麻掸,
    private void nextGeneration() {
        // signal completion of last generation
        trip.signalAll();
        // set up next generation
        count = parties;
        generation = new Generation(); //換一輛車
    }
    //等待超時后氓扛,司機生氣了,就用這個方法把大家叫醒论笔,然后把這輛車標(biāo)記為broken,把所有人趕下車
    private void breakBarrier() {
        generation.broken = true;
        count = parties;
        trip.signalAll();
    }
    //車輛的構(gòu)造器采郎,客車占為車輛設(shè)置的規(guī)則
    public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }
    //同上,只是線程協(xié)同完成后不需要做額外的動作
    public CyclicBarrier(int parties) {
        this(parties, null);
    }
    /**
      一些核心方法
    **/
}

核心方法

CyclicBarrier的核心方法是await狂魔,該方法是線程相互等待的關(guān)鍵蒜埋,它有兩種實現(xiàn),一種是帶等待超時的最楷,一種是不會等待超時:

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這個方法

private int dowait(boolean timed, long nanos)
    throws InterruptedException, BrokenBarrierException,
           TimeoutException {
    final ReentrantLock lock = this.lock;
    lock.lock(); //獲取鎖,這里可以看出線程進(jìn)入等待是單線程的
    try {
        final Generation g = generation;  //當(dāng)前的實例標(biāo)記

        if (g.broken) //新到達(dá)的線程判斷實例是否正常籽孙,如不正常則拋出異常
            throw new BrokenBarrierException();

        if (Thread.interrupted()) {  //新到達(dá)的線程判斷線程中斷狀態(tài)
            breakBarrier();  //如果線程被中斷烈评,則標(biāo)記當(dāng)前實例為中斷,并喚醒所有等待線程
            throw new InterruptedException();
        }


        int index = --count;  //上車成功犯建,還需要上車的人數(shù)減1
        if (index == 0) {  // tripped  //判斷是否是最后一個乘客
            boolean ranAction = false;
            try {
                final Runnable command = barrierCommand;
                if (command != null)
                    command.run();  //最后一個乘客上車成功讲冠,人滿,則執(zhí)行客車統(tǒng)一的規(guī)定
                ranAction = true;
                nextGeneration();  //喚醒本車所有乘客适瓦,并幫助喚來下一輛車竿开。
                return 0;
            } finally {
                if (!ranAction) //如果在執(zhí)行客車統(tǒng)一任務(wù)的時候出了問題,則整趟車標(biāo)記為broken,喚醒所有乘客并趕下車
                    breakBarrier();
            }
        }

        //如果上車的不是最后一個乘客
        for (;;) {
            try {
                if (!timed) 
                    trip.await(); //不需要判斷睡眠時間玻熙,一直睡
                else if (nanos > 0L) //設(shè)置睡眠時間并睡眠
                    nanos = trip.awaitNanos(nanos);
            } catch (InterruptedException ie) {   //如果在失眠過程中被中斷(這里不是被正常喚醒否彩,是被中斷)
                if (g == generation && ! g.broken) { //如果沒有換車,并且客車也沒被標(biāo)記為broken
                    breakBarrier(); //則被中斷的線程(乘客)負(fù)責(zé)將該輛車標(biāo)記為中斷
                    throw ie;
                } else {
                    Thread.currentThread().interrupt();  //如果已經(jīng)換車嗦随,或者被標(biāo)記為了broken,則保存中斷狀態(tài)列荔,繼續(xù)后面的執(zhí)行
                }
            }


            if (g.broken)
                throw new BrokenBarrierException();  //被標(biāo)記為了broken(這可能自己前面標(biāo)的,也可能其它線程標(biāo)的),則所有的線程都拋出異常贴浙。

            if (g != generation)  //如果是正常被喚醒筷转,則直接返回還需上車的人(理論上應(yīng)該是0)
                return index;

            if (timed && nanos <= 0L) { //如果是應(yīng)為等待超時,則拋出TimeoutException
                breakBarrier();
                throw new TimeoutException();
            }
        }
    } finally {
        lock.unlock();
    }
}

總結(jié)

從代碼可以看出悬而,CyclicBarrier的實現(xiàn)是利用條件等待,用到條件等待當(dāng)然就會用到鎖锭汛。
多個線程協(xié)調(diào)過程中笨奠,只要有一個線程被中斷或者發(fā)生異常,則整個協(xié)調(diào)取消唤殴。

CyclicBarrier與CountDownLatch異同點

相同點:
1.都能讓多個線程協(xié)調(diào)般婆,在某一個點上等待

不同點:
1.CyclicBarrier是多個線程自行協(xié)同,當(dāng)線程到達(dá)等待數(shù)量時自動放行朵逝,而CountDownLatch是多個線程阻塞后蔚袍,需要外界條件達(dá)到某種狀態(tài)的時候才會被統(tǒng)一喚醒,即CyclicBarrier只需要各個線程await配名,而CountDownLatch還需要額外是countDown啤咽。
2.實現(xiàn)上,CyclicBarrier是使用獨占鎖+Condition實現(xiàn)的渠脉,而CountDownLatch是自己實現(xiàn)AQS宇整,利用共享鎖的原理實現(xiàn)。
3.CountDownLatch一旦滿足條件后需要重新初始化才能再使用芋膘,而CyclicBarrier可以循環(huán)使用鳞青。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市为朋,隨后出現(xiàn)的幾起案子臂拓,更是在濱河造成了極大的恐慌,老刑警劉巖习寸,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件胶惰,死亡現(xiàn)場離奇詭異,居然都是意外死亡霞溪,警方通過查閱死者的電腦和手機童番,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來威鹿,“玉大人剃斧,你說我怎么就攤上這事『瞿悖” “怎么了幼东?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我根蟹,道長脓杉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任简逮,我火速辦了婚禮球散,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘散庶。我一直安慰自己蕉堰,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布悲龟。 她就那樣靜靜地躺著屋讶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪须教。 梳的紋絲不亂的頭發(fā)上皿渗,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天,我揣著相機與錄音轻腺,去河邊找鬼乐疆。 笑死,一個胖子當(dāng)著我的面吹牛贬养,可吹牛的內(nèi)容都是我干的诀拭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼煤蚌,長吁一口氣:“原來是場噩夢啊……” “哼耕挨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起尉桩,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤筒占,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蜘犁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體翰苫,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年这橙,在試婚紗的時候發(fā)現(xiàn)自己被綠了奏窑。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡屈扎,死狀恐怖埃唯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鹰晨,我是刑警寧澤墨叛,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布止毕,位于F島的核電站,受9級特大地震影響漠趁,放射性物質(zhì)發(fā)生泄漏扁凛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一闯传、第九天 我趴在偏房一處隱蔽的房頂上張望谨朝。 院中可真熱鬧,春花似錦甥绿、人聲如沸字币。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至收叶,卻和暖如春骄呼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背判没。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工蜓萄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人澄峰。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓嫉沽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親俏竞。 傳聞我的和親對象是個殘疾皇子绸硕,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,647評論 2 354