突擊并發(fā)編程JUC系列-并發(fā)工具 CyclicBarrier

突擊并發(fā)編程JUC系列演示代碼地址:
https://github.com/mtcarpenter/JavaTutorial

俗話說(shuō)趁熱要打鐵,上篇中介紹的 CountDownLatch 的基本用法贺嫂, CountDownLatch 計(jì)數(shù)器是一次性的委造,也就是等到計(jì)數(shù)器值變?yōu)?后,再調(diào)用CountDownLatchawaitcountdown方法都會(huì)立刻返回膘盖,這就起不到線程同步的效果了在张。

對(duì)于部分業(yè)務(wù)需要多次循環(huán)使用,就可以使用本章節(jié)的 CyclicBarrierCyclicBarrier的字面意思是可循環(huán)使用(Cyclic)的屏障(Barrier)夷磕, 它同樣擁有 CountDownLatch的功能,CyclicBarrier的字面意思是可循環(huán)使用(Cyclic)的屏障(Barrier)仔沿。它要做的事情是坐桩,讓一組線程到達(dá)一個(gè)屏障(也可以叫同步點(diǎn))時(shí)被阻塞,直到最后一個(gè)線程到達(dá)屏障時(shí)于未,屏障才會(huì)開(kāi)門撕攒,所有被屏障攔截的線程才會(huì)繼續(xù)運(yùn)行陡鹃。

重要方法

  • 構(gòu)造參數(shù)

    • CyclicBarrier(int parties): parties 表示的是參與的線程個(gè)數(shù),這個(gè)數(shù)字通過(guò)構(gòu)造方法進(jìn)行傳遞抖坪。
    • CyclicBarrier(int parties, Runnable barrierAction): 可以接受一個(gè)Runnable參數(shù) ,此參數(shù)表示柵欄動(dòng)作萍鲸,當(dāng)所有線程到達(dá)柵欄后,在所有線程執(zhí)行下一步動(dòng)作前擦俐,運(yùn)行參數(shù)中的動(dòng)作脊阴,這個(gè)動(dòng)作由最后一個(gè)到達(dá)柵欄的線程執(zhí)行。
  • await()

    • await(): 當(dāng)前線程調(diào)用CyclicBarrier的該方法時(shí)會(huì)被阻塞蚯瞧,直到滿足下面條件之一才會(huì)返回: parties個(gè)線程都調(diào)用了await()方法嘿期,也就是線程都到了屏障點(diǎn);其他線程調(diào)用了當(dāng)前線程的interrupt()方法中斷了當(dāng)前線程埋合,則當(dāng)前線程會(huì)拋出InterruptedException異常而返回备徐;與當(dāng)前屏障點(diǎn)關(guān)聯(lián)的Generation對(duì)象的broken標(biāo)志被設(shè)置為true時(shí),會(huì)拋出BrokenBarrierException異常甚颂,然后返回蜜猾。
    • await(long timeout, TimeUnit unit): 當(dāng)前線程調(diào)用CyclicBarrier的該方法時(shí)會(huì)被阻塞,直到滿足下面條件之一才會(huì)返回:parties個(gè)線程都調(diào)用了await()方法振诬,也就是線程都到了屏障點(diǎn)蹭睡,這時(shí)候返回true;設(shè)置的超時(shí)時(shí)間到了后返回false赶么;其他線程調(diào)用當(dāng)前線程的interrupt()方法中斷了當(dāng)前線程肩豁,則當(dāng)前線程會(huì)拋出InterruptedException異常然后返回;與當(dāng)前屏障點(diǎn)關(guān)聯(lián)的Generation對(duì)象的broken標(biāo)志被設(shè)置為true時(shí)辫呻,會(huì)拋出BrokenBarrierException異常清钥,然后返回。

案例上手

分組等待

跟前面countDownLatch一樣通過(guò)學(xué)生的案例進(jìn)行講解印屁,新日小學(xué)的同學(xué)全部已在操場(chǎng)上循捺,但是操場(chǎng)的出口的只有三個(gè),出口同時(shí)只能容納三個(gè)年級(jí)雄人,先整理好的三個(gè)年級(jí)為一組先出从橘,后面的年級(jí)為另一組進(jìn)行出場(chǎng),示例如下:

public class CyclicBarrierExample1 {

    private final static int gradeNum = 6;

    private static CyclicBarrier barrier = new CyclicBarrier(3);

    public static void main(String[] args) throws Exception {
        ExecutorService exec = Executors.newScheduledThreadPool(gradeNum);
        System.out.println("通知础钠、通知恰力,請(qǐng)準(zhǔn)備的年級(jí)先出發(fā).....");
        for (int i = 0; i < gradeNum; i++) {
            TimeUnit.SECONDS.sleep(1);
            int gradeName = i + 1;
            exec.submit(() -> {
                try {
                    wait(gradeName);
                } catch (Exception e) {
                }
            });
        }
        exec.shutdown();
    }

    private static void wait(int gradeName) throws Exception {
        TimeUnit.SECONDS.sleep(1);
        System.out.println(gradeName + "年級(jí)所有同學(xué)來(lái)到了出口......");
        barrier.await();
        System.out.println(gradeName + "年級(jí)所有同學(xué)到出發(fā)");
    }
}

每個(gè)子任務(wù)在執(zhí)行完自己的邏輯后會(huì)調(diào)用await方法。一開(kāi)始計(jì)數(shù)器值為 3 ,相當(dāng)于三個(gè)班級(jí)旗吁,當(dāng)?shù)谝粋€(gè)線程調(diào)用await方法時(shí)踩萎,計(jì)數(shù)器值會(huì)遞減為 1。由于此時(shí)計(jì)數(shù)器值不為 0很钓,所以當(dāng)前線程就到了屏障點(diǎn)而被阻塞香府。然后第二個(gè)線程調(diào)用await 時(shí)董栽,會(huì)進(jìn)入屏障,計(jì)數(shù)器值也會(huì)遞減企孩,現(xiàn)在計(jì)數(shù)器值為 0锭碳,執(zhí)行完畢后退出屏障點(diǎn),繼續(xù)向下運(yùn)行勿璃。

運(yùn)行結(jié)果如下:

通知擒抛、通知,請(qǐng)準(zhǔn)備的年級(jí)先出發(fā).....
1年級(jí)所有同學(xué)來(lái)到了出口......
2年級(jí)所有同學(xué)來(lái)到了出口......
3年級(jí)所有同學(xué)來(lái)到了出口......
3年級(jí)所有同學(xué)到出發(fā)
1年級(jí)所有同學(xué)到出發(fā)
2年級(jí)所有同學(xué)到出發(fā)
4年級(jí)所有同學(xué)來(lái)到了出口......
5年級(jí)所有同學(xué)來(lái)到了出口......
6年級(jí)所有同學(xué)來(lái)到了出口......
6年級(jí)所有同學(xué)到出發(fā)
5年級(jí)所有同學(xué)到出發(fā)
4年級(jí)所有同學(xué)到出發(fā)

超時(shí)等待

為了早日達(dá)到植樹(shù)場(chǎng)地补疑,學(xué)校領(lǐng)導(dǎo)規(guī)定每一個(gè)年級(jí)從操場(chǎng)出去的時(shí)間為 2 秒歧沪,對(duì)于超時(shí)的引起的異常,再進(jìn)行異常處理莲组,示例如下

public class CyclicBarrierExample2 {

    private final static int gradeNum = 6;

    private static CyclicBarrier barrier = new CyclicBarrier(3);

    public static void main(String[] args) throws Exception {
        ExecutorService exec = Executors.newScheduledThreadPool(gradeNum);
        System.out.println("通知诊胞、通知,請(qǐng)準(zhǔn)備的年級(jí)先出發(fā).....");
        for (int i = 0; i < gradeNum; i++) {
            TimeUnit.SECONDS.sleep(1);
            int gradeName = i + 1;
            exec.submit(() -> {
                try {
                    wait(gradeName);
                } catch (Exception e) {
                }
            });
        }
        exec.shutdown();
    }

    private static void wait(int gradeName) throws Exception {
        TimeUnit.SECONDS.sleep(1);
        System.out.println(gradeName + "年級(jí)所有同學(xué)來(lái)到了出口......");
        try {
            barrier.await(2000, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            System.out.println("CyclicBarrier 超時(shí)異常:  " + gradeName + "年級(jí)-" + e);
        }
        System.out.println(gradeName + "年級(jí)所有同學(xué)到出發(fā)");
    }
}

與上面的例子相比锹杈,CyclicBarrier 可以設(shè)置超時(shí)時(shí)間厢钧, 如barrier.await(2000, TimeUnit.MILLISECONDS); 子線程超過(guò)兩秒,就拋出異常嬉橙,根據(jù)自己的業(yè)務(wù)是中斷還是繼續(xù)向下運(yùn)行。
運(yùn)行結(jié)果如下:

通知寥假、通知市框,請(qǐng)準(zhǔn)備的年級(jí)先出發(fā).....
1年級(jí)所有同學(xué)來(lái)到了出口......
2年級(jí)所有同學(xué)來(lái)到了出口......
3年級(jí)所有同學(xué)來(lái)到了出口......
3年級(jí)所有同學(xué)到出發(fā)
1年級(jí)所有同學(xué)到出發(fā)
2年級(jí)所有同學(xué)到出發(fā)
4年級(jí)所有同學(xué)來(lái)到了出口......
5年級(jí)所有同學(xué)來(lái)到了出口......
6年級(jí)所有同學(xué)來(lái)到了出口......
CyclicBarrier 超時(shí)異常:  4年級(jí)-java.util.concurrent.TimeoutException
4年級(jí)所有同學(xué)到出發(fā)
CyclicBarrier 超時(shí)異常:  5年級(jí)-java.util.concurrent.BrokenBarrierException
5年級(jí)所有同學(xué)到出發(fā)
CyclicBarrier 超時(shí)異常:  6年級(jí)-java.util.concurrent.BrokenBarrierException
6年級(jí)所有同學(xué)到出發(fā)

回調(diào)

每一個(gè)年級(jí)達(dá)到入口之后,匯報(bào)給領(lǐng)導(dǎo)糕韧,領(lǐng)導(dǎo)進(jìn)行接下來(lái)的安排枫振。示例如下:

public class CyclicBarrierExample3 {

    private final static int gradeNum = 6;

    private static CyclicBarrier barrier = new CyclicBarrier(3, new Runnable() {
        @Override
        public void run() {
            System.out.println("******所有子線程達(dá)到屏障******");
        }
    });

    public static void main(String[] args) throws Exception {
        ExecutorService exec = Executors.newScheduledThreadPool(gradeNum);
        System.out.println("通知、通知萤彩,請(qǐng)準(zhǔn)備的年級(jí)先出發(fā).....");
        for (int i = 0; i < gradeNum; i++) {
            TimeUnit.SECONDS.sleep(1);
            int gradeName = i + 1;
            exec.submit(() -> {
                try {
                    wait(gradeName);
                } catch (Exception e) {
                }
            });
        }
        exec.shutdown();
    }

    private static void wait(int gradeName) throws Exception {
        TimeUnit.SECONDS.sleep(1);
        System.out.println(gradeName + "年級(jí)所有同學(xué)來(lái)到了出口......");
        barrier.await();
        System.out.println(gradeName + "年級(jí)所有同學(xué)到出發(fā)");
    }
}

如上代碼創(chuàng)建了一個(gè) CyclicBarrier 對(duì)象粪滤,其第一個(gè)參數(shù)為計(jì)數(shù)器初始值,第二個(gè)參數(shù)Runable是當(dāng)計(jì)數(shù)器值為 0 是需要執(zhí)行的任務(wù)雀扶。當(dāng)計(jì)數(shù)器值為 0杖小,這時(shí)就會(huì)去執(zhí)行CyclicBarrier 構(gòu)造函數(shù)中的任務(wù),執(zhí)行完畢后退出屏障點(diǎn)愚墓,繼續(xù)向下運(yùn)行予权。

CyclicBarrierCountDownLatch區(qū)別

CyclicBarrierCountDownLatch可能容易混淆,我們強(qiáng)調(diào)下它們的區(qū)別浪册。

  • CountDownLatch的參與線程是有不同角色的扫腺,有的負(fù)責(zé)倒計(jì)時(shí),有的在等待倒計(jì)時(shí)變?yōu)?0村象,負(fù)責(zé)倒計(jì)時(shí)和等待倒計(jì)時(shí)的線程都可以有多個(gè)笆环,用于不同角色線程間的同步攒至。

  • CyclicBarrier的參與線程角色是一樣的,用于同一角色線程間的協(xié)調(diào)一致躁劣。

  • CountDownLatch是一次性的迫吐,而CyclicBarrier是可以重復(fù)利用的。


歡迎關(guān)注公眾號(hào) 山間木匠 习绢, 我是小春哥渠抹,從事 Java 后端開(kāi)發(fā),會(huì)一點(diǎn)前端闪萄、通過(guò)持續(xù)輸出系列技術(shù)文章以文會(huì)友梧却,如果本文能為您提供幫助,歡迎大家關(guān)注败去、 點(diǎn)贊放航、分享支持,我們下期再見(jiàn)圆裕!<br />

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末广鳍,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子吓妆,更是在濱河造成了極大的恐慌赊时,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件行拢,死亡現(xiàn)場(chǎng)離奇詭異祖秒,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)舟奠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門竭缝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人沼瘫,你說(shuō)我怎么就攤上這事抬纸。” “怎么了耿戚?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵湿故,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我溅话,道長(zhǎng)晓锻,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任飞几,我火速辦了婚禮砚哆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己躁锁,他們只是感情好纷铣,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著战转,像睡著了一般搜立。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上槐秧,一...
    開(kāi)封第一講書(shū)人閱讀 51,737評(píng)論 1 305
  • 那天啄踊,我揣著相機(jī)與錄音,去河邊找鬼刁标。 笑死颠通,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的膀懈。 我是一名探鬼主播顿锰,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼启搂!你這毒婦竟也來(lái)了硼控?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤胳赌,失蹤者是張志新(化名)和其女友劉穎牢撼,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體疑苫,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡浪默,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了缀匕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡碰逸,死狀恐怖乡小,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情饵史,我是刑警寧澤满钟,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站胳喷,受9級(jí)特大地震影響湃番,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜吭露,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一吠撮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧讲竿,春花似錦泥兰、人聲如沸弄屡。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)膀捷。三九已至,卻和暖如春削彬,著一層夾襖步出監(jiān)牢的瞬間全庸,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工融痛, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留壶笼,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓酌心,卻偏偏與公主長(zhǎng)得像拌消,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子安券,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355