Java多線程之CyclicBarrier
這里就JUC包中的CyclicBarrier類(lèi)做相關(guān)介紹

概述
JUC中的CyclicBarrier類(lèi)是一個(gè)并發(fā)控制工具。其可以使線程在柵欄處進(jìn)行等待。當(dāng)指定數(shù)量的線程全部到達(dá)柵欄處后柵欄才會(huì)打開(kāi),從而使各線程結(jié)束阻塞繼續(xù)向下執(zhí)行。其主要方法如下所示,可以看到在線程全部到達(dá)柵欄時(shí),還可以通過(guò)barrierAction參數(shù)設(shè)置準(zhǔn)備打開(kāi)柵欄前需執(zhí)行的任務(wù)。其中,該任務(wù)由最后一個(gè)到達(dá)柵欄的線程負(fù)責(zé)執(zhí)行。具體地,線程調(diào)用await方法實(shí)現(xiàn)告訴CyclicBarrier自己已經(jīng)到達(dá)柵欄處,并阻塞等待柵欄打開(kāi)
//?創(chuàng)建一個(gè)指定計(jì)數(shù)器值的CyclicBarrier實(shí)例
public?CyclicBarrier(int?parties);
//?創(chuàng)建一個(gè)指定計(jì)數(shù)器值的CyclicBarrier實(shí)例,?并指定柵欄打開(kāi)前需執(zhí)行的任務(wù)
public?CyclicBarrier(int?parties,?Runnable?barrierAction);
//?線程阻塞等待柵欄打開(kāi)
public?int?await()?throws?InterruptedException,?BrokenBarrierException;
//?支持超時(shí)的await方法
public?int?await(long?timeout,?TimeUnit?unit)?throws?InterruptedException,?BrokenBarrierException,?TimeoutException;
//?喚醒其他正在柵欄處被阻塞的線程(即拋出BrokenBarrierException異常),?同時(shí)將CyclicBarrier實(shí)例恢復(fù)為初始化狀態(tài),以便下一次使用
public?void?reset();
基本實(shí)踐
下面即是一個(gè)CyclicBarrier的基本實(shí)踐示例
public?class?CyclicBarrierTest1?{
????private?static?DateTimeFormatter?formatter?=?DateTimeFormatter.ofPattern("HH:mm:ss");
????@Test
????public?void?test1()?throws?InterruptedException?{
????????ExecutorService?threadPool?=?Executors.newFixedThreadPool(10);
????????Runnable?initTask?=?()?->?{
????????????info("---------------------------------");
????????};
????????CyclicBarrier?cyclicBarrier?=?new?CyclicBarrier(3,?initTask);
????????Stream.of("張三","李四","王二")
????????????.map(?name?->?new?PlayGame(name,?cyclicBarrier)?)
????????????.forEach(playGame?->?{
????????????????threadPool.execute(playGame);
????????????}?);
????????//?主線程等待所有任務(wù)執(zhí)行完畢
????????try{?Thread.sleep(?120*1000?);?}?catch?(Exception?e)?{}
????????info("Game?Over");
????}
????/**
?????*?打印信息
?????*?@param?msg
?????*/
????private?static?void?info(String?msg)?{
????????String?time?=?formatter.format(LocalTime.now());
????????String?threadName?=?Thread.currentThread().getName();
????????String?log?=?"["+time+"]?"+?msg+"?<"+threadName+">";
????????System.out.println(log);
????}
????/**
?????*?模擬業(yè)務(wù)耗時(shí)
?????*/
????private?static?void?doSomeWork()?{
????????try{
????????????Integer?second?=?RandomUtils.nextInt(3,20);
????????????System.out.println("second:?"?+?second);
????????????Thread.sleep(?second?*?1000?);
????????}catch?(Exception?e)?{
????????????System.out.println(?"Happen?Exception:?"?+?e.getMessage());
????????}
????}
????@AllArgsConstructor
????private?static?class?PlayGame?implements?Runnable{
????????private?String?name;
????????private?CyclicBarrier?cyclicBarrier;
????????@Override
????????public?void?run()?{
????????????//?模擬業(yè)務(wù)耗時(shí)
????????????doSomeWork();
????????????info(name?+?"?上線");
????????????//?阻塞等待其他玩家上線
????????????try{
????????????????cyclicBarrier.await();
????????????}catch?(Exception?e)?{
????????????????System.out.println(?"Happen?Exception:?"?+?e);
????????????}
????????????info(name?+?"?選擇角色?開(kāi)始");
????????????//?模擬業(yè)務(wù)耗時(shí)
????????????doSomeWork();
????????????info(name?+?"?選擇角色?結(jié)束");
????????????//?阻塞等待其他玩家選擇角色
????????????try{
????????????????cyclicBarrier.await();
????????????}catch?(Exception?e)?{
????????????????System.out.println(?"Happen?Exception:?"?+?e);
????????????}
????????????info(name?+?"?開(kāi)始游戲");
????????}
????}
}
從測(cè)試結(jié)果可以看出,當(dāng)用戶 開(kāi)始選擇角色 或 開(kāi)始游戲時(shí),各線程是同時(shí)開(kāi)始的。至此也可以看出其與CountDownLatch的顯著區(qū)別,后者是一次性的,而前者CyclicBarrier則可以重復(fù)使用

基本原理
通過(guò)上面的代碼示例,可以看到CyclicBarrier與CountDownLatch相比功能很類(lèi)似。只不過(guò)前者可以重復(fù)使用,而后者則是一次性的。但二者在實(shí)現(xiàn)上卻大相徑庭,CountDownLatch是直接基于AQS實(shí)現(xiàn)的。而CyclicBarrier則是利用ReentrantLock、Condition進(jìn)行實(shí)現(xiàn)的。具體地,當(dāng)線程調(diào)用CyclicBarrier的await方法時(shí),如果未達(dá)到指定數(shù)量時(shí),則是通過(guò)Condition條件變量的await方法進(jìn)行阻塞的;如果是最后一個(gè)線程則會(huì)通過(guò)Condition條件變量的signalAll方法來(lái)喚醒所有被阻塞的線程
與此同時(shí),由于CyclicBarrier是可重復(fù)使用的。故每一輪結(jié)束后,其內(nèi)部會(huì)通過(guò)nextGeneration方法生成所謂的下一代CyclicBarrier。本質(zhì)上相當(dāng)于重新實(shí)例化了一次CyclicBarrier
Note
在實(shí)際使用CyclicBarrier過(guò)程中,需要非常小心處理BrokenBarrierException異常。本文示例代碼為了簡(jiǎn)便,故省略了異常處理過(guò)程。因?yàn)榘l(fā)生該異常說(shuō)明柵欄被損壞了。推薦的處理措施有:一方面,調(diào)用CyclicBarrier的reset方法,來(lái)喚醒其他由于調(diào)用await方法而被阻塞的線程以避免一直被阻塞,同時(shí)將CyclicBarrier實(shí)例恢復(fù)至初始化狀態(tài);另一方面,推薦使用具有超時(shí)機(jī)制的await方法,以避免線程被永久性阻塞
參考文獻(xiàn)
Java并發(fā)編程之美 翟陸續(xù)、薛賓田著
