Java并發(fā)實(shí)例之CyclicBarrier的使用
最近一直整并發(fā)這塊東西,順便寫點(diǎn)Java并發(fā)的例子,給大家做個分享,也強(qiáng)化下自己記憶,如果有什么錯誤或者不當(dāng)?shù)牡胤剑瑲g迎大家斧正。
CyclicBarrier是一種多線程并發(fā)控制實(shí)用工具,和CountDownLatch非常類似,它也可以實(shí)現(xiàn)線程間的計(jì)數(shù)等待,但是它的功能比CountDownLatch更加復(fù)雜且強(qiáng)大。
CyclicBarrier的介紹
CyclicBarrier 的字面意思是可循環(huán)(Cyclic)使用的屏障(Barrier)。它要做的事情是,讓一組線程到達(dá)一個屏障(也可以叫同步點(diǎn))時被阻塞,直到最后一個線程到達(dá)屏障時,屏障才會開門,所有被屏障攔截的線程才會繼續(xù)干活。線程進(jìn)入屏障通過CyclicBarrier的await()方法。
CyclicBarrier默認(rèn)的構(gòu)造方法是CyclicBarrier(int parties),其參數(shù)表示屏障攔截的線程數(shù)量,每個線程調(diào)用await方法告訴CyclicBarrier我已經(jīng)到達(dá)了屏障,然后當(dāng)前線程被阻塞。
CyclicBarrier還提供一個更高級的構(gòu)造函數(shù)CyclicBarrier(int parties, Runnable barrierAction),用于在線程到達(dá)屏障時,優(yōu)先執(zhí)行barrierAction這個Runnable對象,方便處理更復(fù)雜的業(yè)務(wù)場景。
public CyclicBarrier(int parties) {
this(parties, null);
}
public int getParties() {
return parties;
}
實(shí)現(xiàn)原理:在CyclicBarrier的內(nèi)部定義了一個Lock對象,每當(dāng)一個線程調(diào)用CyclicBarrier的await方法時,將剩余攔截的線程數(shù)減1,然后判斷剩余攔截?cái)?shù)是否為0,如果不是,進(jìn)入Lock對象的條件隊(duì)列等待。如果是,執(zhí)行barrierAction對象的Runnable方法,然后將鎖的條件隊(duì)列中的所有線程放入鎖等待隊(duì)列中,這些線程會依次的獲取鎖、釋放鎖,接著先從await方法返回,再從CyclicBarrier的await方法中返回。
CyclicBarrier主要用于一組線程之間的相互等待,而CountDownLatch一般用于一組線程等待另一組些線程。實(shí)際上可以通過CountDownLatch的countDown()和await()來實(shí)現(xiàn)CyclicBarrier的功能。即 CountDownLatch中的countDown()+await() = CyclicBarrier中的await()。注意:在一個線程中先調(diào)用countDown(),然后調(diào)用await()。
構(gòu)造函數(shù)CyclicBarrier可以理解為循環(huán)柵欄,這個計(jì)數(shù)器可以反復(fù)使用。比如,假設(shè)我們將計(jì)數(shù)器設(shè)置為10,那么湊齊第一批10個線程后,計(jì)數(shù)器就會歸零,然后接著湊齊下一批10個線程,這就是它的內(nèi)在含義。
LOL和王者榮耀的玩家很多,許多人應(yīng)該都有打大龍的經(jīng)歷,話說前期大家打算一起去偷大龍,由于前期大家都比較弱,需要五個人都齊了才能打大龍,這樣程序該如何實(shí)現(xiàn)呢?本人很菜,開始我的代碼是這么寫的(哈哈大家不要糾結(jié)我的時間):
public class KillDragon {
/**
* 模擬打野去打大龍
*/
public static void dayePlayDragon(){
System.out.println("打野在去打大龍的路上,需要10s");
}
/**
* 模擬上單去打大龍
*/
public static void shangdanPlayDragon(){
System.out.println("上單在去打大龍的路上,需要10s");
}
/**
* 模擬中單去打大龍
*/
public static void zhongdanPlayDragon(){
System.out.println("中單在去打大龍的路上,需要10s");
}
/**
* 模擬ADC和輔助去打大龍
*/
public static void adcAndFuzhuPlayDragon(){
System.out.println("ADC和輔助在去打大龍的路上,需要10s");
}
/**
* 模擬大家一起去打大龍
*/
public static void killDragon()
{
System.out.println("打大龍...");
}
public static void main(String[] args)
{
dayePlayDragon();
shangdanPlayDragon();
zhongdanPlayDragon();
adcAndFuzhuPlayDragon();
killDragon();
}
結(jié)果如下:
打野在去打大龍的路上,需要10s 上單在去打大龍的路上,需要10s 中單在去打大龍的路上,需要10s ADC和輔助在去打大龍的路上,需要10s 打大龍...
這完了,大家在路上的時間就花了40s了,顯然是錯誤的。要是都這么干,對方把你塔都要偷光了。不行得改進(jìn)下,怎么改呢,多線程并發(fā)執(zhí)行,如是我改成了下面這樣的,用volatile關(guān)鍵字。
private static volatile int i = 4;
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
long start = System.currentTimeMillis();
while (i!=0){
}
while (i==0) {
killDragon();
i--;
long t = System.currentTimeMillis() - start;
System.out.println("總共耗時:"+t+"毫秒");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
dayePlayDragon();
try {
Thread.sleep(10000);
i--;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
shangdanPlayDragon();
try {
Thread.sleep(10000);
i--;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
zhongdanPlayDragon();
try {
Thread.sleep(10000);
i--;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
adcAndFuzhuPlayDragon();
try {
Thread.sleep(10000);
i--;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
結(jié)果如下:
打野在去打大龍的路上,需要10s 上單在去打大龍的路上,需要10s 中單在去打大龍的路上,需要10s ADC和輔助在去打大龍的路上,需要10s 打大龍... 總共耗時:10005毫秒
結(jié)果似乎還不錯,但是處理起來實(shí)在是有點(diǎn)麻煩,需要 while (i!=0)一直在那循環(huán)著。這時候?qū)W到了用 CyclicBarrier來處理,代碼如下:
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(5);
new Thread(new Runnable() {
@Override
public void run() {
long start = System.currentTimeMillis();
try {
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
killDragon();
long t = System.currentTimeMillis() - start;
System.out.println("總共耗時:"+t+"毫秒");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
dayePlayDragon();
try {
Thread.sleep(10000);
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
shangdanPlayDragon();
try {
Thread.sleep(10000);
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
zhongdanPlayDragon();
try {
Thread.sleep(10000);
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
adcAndFuzhuPlayDragon();
try {
Thread.sleep(10000);
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
大家都沒到達(dá)之前都等待,結(jié)果如下:
打野在去打大龍的路上,需要10s 上單在去打大龍的路上,需要10s 中單在去打大龍的路上,需要10s ADC和輔助在去打大龍的路上,需要10s 打大龍... 總共耗時:10002毫秒
CyclicBarrier相當(dāng)于線程的計(jì)數(shù)器:
CyclicBarrier初始化時規(guī)定一個數(shù)目,然后計(jì)算調(diào)用了CyclicBarrier.await()進(jìn)入等待的線程數(shù)。當(dāng)線程數(shù)達(dá)到了這個數(shù)目時,所有進(jìn)入等待狀態(tài)的線程被喚醒并繼續(xù)。
CyclicBarrier就象它名字的意思一樣,可看成是個障礙, 所有的線程必須到齊后才能一起通過這個障礙。
CyclicBarrier初始時還可帶一個Runnable的參數(shù), 此Runnable任務(wù)在CyclicBarrier的數(shù)目達(dá)到后,所有其它線程被喚醒前被執(zhí)行。
當(dāng)然這樣使用CyclicBarrier和使用CountDownLatch是沒什么區(qū)別的,正如前文所說的CyclicBarrier的功能更加的復(fù)雜且強(qiáng)大。給大家看一個《實(shí)戰(zhàn)Java高并發(fā)程序設(shè)計(jì)》一書上的一個例子。
比如:司令下達(dá)命令,要求10個士兵去一起完成一項(xiàng)任務(wù)。這時,就會要求10個士兵先集合報(bào)道,接著,一起雄赳赳氣昂昂地去執(zhí)行任務(wù)。當(dāng)10個士兵都執(zhí)行完了任務(wù),那么司機(jī)就可以對外宣稱,任務(wù)完成。相比CountDownLatch,CyclicBarrier可以接受一個參數(shù)作為BarrierAction。所謂的BarrierAction就是當(dāng)計(jì)數(shù)器一次計(jì)數(shù)完成后,系統(tǒng)會執(zhí)行的動作。如下構(gòu)造函數(shù),其中,parties表示技術(shù)總數(shù),也就是參與的線程總數(shù)。
public CyclicBarrier(int parties, Runnable barrierAction)
下面示例演示了上述任務(wù)場景
public class CyclicBarrierDemo {
public static class Soldier implements Runnable {
private String soldier;
private final CyclicBarrier cyclicBarrier;
public Soldier(CyclicBarrier cyclicBarrier, String soldier) {
this.soldier = soldier;
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
try {
cyclicBarrier.await();
doWork();
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
void doWork() {
try {
Thread.sleep(Math.abs(new Random().nextInt() % 10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(soldier + ":任務(wù)完成");
}
}
public static class BarrierRun implements Runnable {
boolean flag;
int N;
public BarrierRun(boolean flag, int N) {
this.flag = flag;
this.N = N;
}
@Override
public void run() {
if (flag) {
System.out.println("司令:[士兵" + N + "個,任務(wù)完成!");
} else {
System.out.println("司令:[士兵" + N + "個,集合完畢!");
flag = true;
}
}
}
public static void main(String args[]) {
final int N = 10;
Thread[] allSoldier = new Thread[N];
boolean flag = false;
CyclicBarrier cyclicBarrier = new CyclicBarrier(N, new BarrierRun(flag, N));
System.out.println("集合隊(duì)伍!");
for (int i = 0; i < N; i++) {
System.out.println("士兵" + i + "報(bào)道!");
allSoldier[i] = new Thread(new Soldier(cyclicBarrier, "士兵" + i));
allSoldier[i].start();
}
}
}
執(zhí)行結(jié)果如下:
集合隊(duì)伍! 士兵0報(bào)道! 士兵1報(bào)道! 士兵2報(bào)道! 士兵3報(bào)道! 士兵4報(bào)道! 士兵5報(bào)道! 士兵6報(bào)道! 士兵7報(bào)道! 士兵8報(bào)道! 士兵9報(bào)道! 司令:[士兵10個,集合完畢! 士兵0:任務(wù)完成 士兵2:任務(wù)完成 士兵9:任務(wù)完成 士兵3:任務(wù)完成 士兵7:任務(wù)完成 士兵8:任務(wù)完成 士兵1:任務(wù)完成 士兵4:任務(wù)完成 士兵5:任務(wù)完成 士兵6:任務(wù)完成 司令:[士兵10個,任務(wù)完成!
總結(jié)
以上就是本文關(guān)于Java并發(fā)實(shí)例之CyclicBarrier的使用的全部內(nèi)容,希望對大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站:
Javaweb應(yīng)用使用限流處理大量的并發(fā)請求詳解
java并發(fā)學(xué)習(xí)之BlockingQueue實(shí)現(xiàn)生產(chǎn)者消費(fèi)者詳解
如有不足之處,歡迎留言指出。期待您的寶貴意見!
相關(guān)文章
SpringBoot添加SSL證書,開啟HTTPS方式(單向認(rèn)證服務(wù)端)
這篇文章主要介紹了SpringBoot添加SSL證書,開啟HTTPS方式(單向認(rèn)證服務(wù)端),具有很好的參考價(jià)值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03
使用@PathVariable時候無法將參數(shù)映射到變量中的解決
這篇文章主要介紹了使用@PathVariable時候無法將參數(shù)映射到變量中的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
Spring創(chuàng)建Bean的過程Debug的詳細(xì)流程
這篇文章主要介紹了Spring創(chuàng)建Bean的過程Debug的流程,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11
解決SpringBoot整合ElasticSearch遇到的連接問題
這篇文章主要介紹了解決SpringBoot整合ElasticSearch遇到的連接問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
SpringBoot詳解整合MyBatis過程中可能遇到的問題
因?yàn)镾pring Boot框架開發(fā)的便利性,所以實(shí)現(xiàn)Spring Boot與數(shù)據(jù)訪問層框架(例如MyBatis)的整合非常簡單,主要是引入對應(yīng)的依賴啟動器,并進(jìn)行數(shù)據(jù)庫相關(guān)參數(shù)設(shè)置即可2022-07-07
Java之多個線程順序循環(huán)執(zhí)行的幾種實(shí)現(xiàn)
這篇文章主要介紹了Java之多個線程順序循環(huán)執(zhí)行的幾種實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09

