突擊并發(fā)編程JUC系列演示代碼地址:
https://github.com/mtcarpenter/JavaTutorial
俗話說(shuō)趁熱要打鐵,上篇中介紹的 CountDownLatch
的基本用法贺嫂, CountDownLatch
計(jì)數(shù)器是一次性的委造,也就是等到計(jì)數(shù)器值變?yōu)?后,再調(diào)用CountDownLatch
的await
和countdown
方法都會(huì)立刻返回膘盖,這就起不到線程同步的效果了在张。
對(duì)于部分業(yè)務(wù)需要多次循環(huán)使用,就可以使用本章節(jié)的 CyclicBarrier
,CyclicBarrier
的字面意思是可循環(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)行予权。
CyclicBarrier
與CountDownLatch
區(qū)別
CyclicBarrier
與CountDownLatch
可能容易混淆,我們強(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 />