barrier(屏障)與互斥量、讀寫鎖、自旋鎖不同聘萨,它不是用來保護臨界區(qū)的。相反嘉赎,它跟條件變量一樣,是用來協(xié)同多線程一起工作的于樟。
??條件變量是多線程間傳遞狀態(tài)的改變來達到協(xié)同工作的效果公条。屏障是多線程各自做自己的工作,如果某一線程完成了工作迂曲,就等待在屏障那里靶橱,直到其他線程的工作都完成了,再一起做別的事奢米。舉個通俗的例子:
??1.對于條件變量抓韩。在接力賽跑里纠永,1號隊員開始跑的時候鬓长,2,3尝江,4號隊員都站著不動涉波,直到1號隊員跑完一圈,把接力棒給2號隊員炭序,2號隊員收到接力棒后就可以跑了啤覆,跑完再給3號隊員。這里這個接力棒就相當于條件變量惭聂,條件滿足后就可以由下一個隊員(線程)跑窗声。
??2.對于屏障:在百米賽跑里,比賽沒開始之前辜纲,每個運動員都在賽場上自由活動笨觅,有的熱身,有的喝水耕腾,有的跟教練談論见剩。比賽快開始時,準備完畢的運動員就預備在起跑線上扫俺,如果有個運動員還沒準備完(除去特殊情況)苍苞,他們就一直等,直到運動員都在起跑線上狼纬,裁判喊口號后再開始跑羹呵。這里的起跑線就是屏障骂际,做完準備工作的運動員都等在起跑線,直到其他運動員也把準備工作做完冈欢。
java.util.concurrent.CyclicBarrier類是一個同步機制方援。它可以通過一些算法來同步線程處理的過程。換言之涛癌,就是所有的線程必須等待對方犯戏,直到所有的線程到達屏障,然后繼續(xù)運行拳话。之所以叫做“循環(huán)屏障”先匪,是因為這個屏障可以被重復使用。
CyclicBarrier有兩個構(gòu)造參數(shù)弃衍,分別是:
CyclicBarrier(int parties)
創(chuàng)建一個新的 CyclicBarrier呀非,它將在給定數(shù)量的參與者(線程)處于等待狀態(tài)時啟動,但它不會在啟動 barrier 時執(zhí)行預定義的操作镜盯。
??CyclicBarrier(int parties, Runnable barrierAction)
創(chuàng)建一個新的 CyclicBarrier岸裙,它將在給定數(shù)量的參與者(線程)處于等待狀態(tài)時啟動,并在啟動 barrier 時執(zhí)行給定的屏障操作速缆,該操作由最后一個進入 barrier 的線程執(zhí)行降允。
讓線程在CyclicBarrier中等待
有兩個方法可以讓線程在CyclicBarrier處等待:
barrier.await()
;
??barrier.await(10, TimeUnit.SECONDS);
??第二個方法指線程等待的超時時間,當出現(xiàn)等待超時的時候艺糜,當前線程會被釋放,但會像其他線程傳播出BrokenBarrierException異常剧董。
??所有線程在CyclicBarrier等待,是指:
? 最后一個線程到達(調(diào)用await方法)
? 一個線程被被另外一個線程中斷(另外一個線程調(diào)用了這個現(xiàn)場的interrupt()方法)
? 其中一個等待的線程被中斷
? 其中一個等待的線程超時
? 一個外部的線程調(diào)用了CyclicBarrier.reset()方法破停。
下面以5個線程模擬5個運動員翅楼。運動員在賽跑的時候都會準備一段時間,當裁判發(fā)現(xiàn)所有的運動員都準備完畢的時候真慢,就舉起發(fā)令槍毅臊,比賽開始。
package thread;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* 模擬運動員
**/
public class MyThread extends Thread {
private CyclicBarrier cyclicBarrier;
private String name;
public MyThread(CyclicBarrier cyclicBarrier, String name) {
super();
this.cyclicBarrier = cyclicBarrier;
this.name = name;
}
@Override
public void run() {
System.out.println(name + "開始準備");
try {
Thread.currentThread().sleep(5000);
System.out.println(name + "準備完畢黑界!等待發(fā)令槍");
try {
cyclicBarrier.await();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//測試類
public class Test {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(5, new Runnable() {
@Override
public void run() {
System.out.println("發(fā)令槍響了管嬉,跑!");
}
});
for (int i = 0; i < 5; i++) {
new MyThread(barrier, "運動員" + i + "號").start();
}
}
}
當執(zhí)行測試類的時候园爷,輸出如下的結(jié)果(順序每次執(zhí)行可能會不太一樣):
運動員1號開始準備
運動員3號開始準備
運動員2號開始準備
運動員0號開始準備
運動員4號開始準備
運動員1號準備完畢宠蚂!等待發(fā)令槍
運動員4號準備完畢!等待發(fā)令槍
運動員0號準備完畢童社!等待發(fā)令槍
運動員3號準備完畢求厕!等待發(fā)令槍
運動員2號準備完畢!等待發(fā)令槍
發(fā)令槍響了,跑!
從輸出可以看到,當給定數(shù)量的參與者(線程)調(diào)用了await()方法之后呀癣,屏障放開美浦,CyclicBarrier中的屏障動作被觸發(fā)了。如果沒有達到指定的數(shù)量项栏,就會一直被阻塞浦辨。
Barrier被破壞
BrokenBarrierException如果在參與者(線程)在等待的過程中,Barrier被破壞沼沈,就會拋出BrokenBarrierException流酬。可以用isBroken()方法檢測Barrier是否被破壞列另。
??1.如果有線程已經(jīng)處于等待狀態(tài)芽腾,調(diào)用reset方法會導致已經(jīng)在等待的線程出現(xiàn)BrokenBarrierException異常。并且由于出現(xiàn)了BrokenBarrierException页衙,將會導致始終無法等待摊滔。
比如,五個運動員店乐,其中一個在等待發(fā)令槍的過程中錯誤地接收到裁判傳過來的指令艰躺,導致這個運動員以為今天比賽取消就離開了賽場。但是其他運動員都領(lǐng)會的裁判正確的指令眨八,剩余的運動員在起跑線上無限地等待下去腺兴,并且裁判看到運動員沒有到齊,也不會打發(fā)令槍踪古。
package thread;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class MyThread extends Thread {
private CyclicBarrier cyclicBarrier;
private String name;
private int ID;
public MyThread(CyclicBarrier cyclicBarrier, String name,int ID) {
super();
this.cyclicBarrier = cyclicBarrier;
this.name = name;
this.ID=ID;
}
@Override
public void run() {
System.out.println(name + "開始準備");
try {
Thread.sleep(ID*1000); //不同運動員準備時間不一樣含长,方便模擬不同情況
System.out.println(name + "準備完畢!在起跑線等待發(fā)令槍");
try {
cyclicBarrier.await();
System.out.println(name + "跑完了路程伏穆!");
} catch (BrokenBarrierException e) {
e.printStackTrace();
System.out.println(name+"看不見起跑線了");
}
System.out.println(name+"退場!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
CyclicBarrier barrier = new CyclicBarrier(5, new Runnable() {
@Override
public void run() {
System.out.println("發(fā)令槍響了纷纫,跑枕扫!");
}
});
for (int i = 0; i < 5; i++) {
new MyThread(barrier, "運動員" + i + "號", i).start();
}
Thread.sleep(1000);
barrier.reset();
}
}
輸出結(jié)果:
運動員0號開始準備
運動員1號開始準備
運動員2號開始準備
運動員3號開始準備
運動員4號開始準備
運動員0號準備完畢!在起跑線等待發(fā)令槍
運動員1號準備完畢辱魁!在起跑線等待發(fā)令槍
java.util.concurrent.BrokenBarrierException
運動員0號看不見起跑線了
運動員0號退場烟瞧!
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:250)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at thread.MyThread.run(MyThread.java:27)
運動員2號準備完畢!在起跑線等待發(fā)令槍
運動員3號準備完畢染簇!在起跑線等待發(fā)令槍
運動員4號準備完畢参滴!在起跑線等待發(fā)令槍
從輸出可以看到,運動員0號在等待的過程中锻弓,主線程調(diào)用了reset方法砾赔,導致拋出BrokenBarrierException異常。但是其他線程并沒有受到影響,它們會一直等待下去暴心,從而一直被阻塞妓盲。
2.如果在等待的過程中,線程被中斷专普,也會拋出BrokenBarrierException異常悯衬,并且這個異常會傳播到其他所有的線程。
package thread;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CyclicBarrier;
public class Test {
static Map<Integer,Thread> threads=new HashMap<>();
public static void main(String[] args) throws InterruptedException {
CyclicBarrier barrier = new CyclicBarrier(5, new Runnable() {
@Override
public void run() {
System.out.println("發(fā)令槍響了檀夹,跑筋粗!");
}
});
for (int i = 0; i < 5; i++) {
MyThread t = new MyThread(barrier, "運動員" + i + "號", i);
threads.put(i, t);
t.start();
}
Thread.sleep(3000);
threads.get(1).interrupt();
}
}
輸出:
運動員0號開始準備
運動員2號開始準備
運動員3號開始準備
運動員1號開始準備
運動員0號準備完畢!在起跑線等待發(fā)令槍
運動員4號開始準備
運動員1號準備完畢炸渡!在起跑線等待發(fā)令槍
運動員2號準備完畢亏狰!在起跑線等待發(fā)令槍
運動員3號準備完畢!在起跑線等待發(fā)令槍
java.lang.InterruptedException
運動員3號看不見起跑線了
運動員3號退場偶摔!
運動員2號看不見起跑線了
運動員2號退場暇唾!
運動員0號看不見起跑線了
運動員0號退場!
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.reportInterruptAfterWait(AbstractQueuedSynchronizer.java:2014)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2048)
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:234)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at thread.MyThread.run(MyThread.java:27)
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:250)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at thread.MyThread.run(MyThread.java:27)
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:250)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at thread.MyThread.run(MyThread.java:27)
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:250)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at thread.MyThread.run(MyThread.java:27)
運動員4號準備完畢辰斋!在起跑線等待發(fā)令槍
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:207)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at thread.MyThread.run(MyThread.java:27)
運動員4號看不見起跑線了
運動員4號退場策州!
從輸出可以看到,其中一個線程被中斷宫仗,那么所有的運動員都退場了够挂。
3.如果在執(zhí)行屏障操作過程中發(fā)生異常,則該異常將傳播到當前線程中藕夫,其他線程會拋出BrokenBarrierException孽糖,屏障被損壞。
這個就好比運動員都沒有問題毅贮,而是裁判出問題了办悟。裁判權(quán)力比較大,直接告訴所有的運動員滩褥,今天不比賽了病蛉,你們都回家吧!
package thread;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CyclicBarrier;
public class Test {
static Map<Integer, Thread> threads = new HashMap<>();
public static void main(String[] args) throws InterruptedException {
CyclicBarrier barrier = new CyclicBarrier(5, new Runnable() {
@Override
public void run() {
String str = null;
str.substring(0, 1);
System.out.println("發(fā)令槍響了瑰煎,跑铺然!");
}
});
for (int i = 0; i < 5; i++) {
MyThread t = new MyThread(barrier, "運動員" + i + "號", i);
threads.put(i, t);
t.start();
}
}
}
輸出:
運動員0號開始準備
運動員3號開始準備
運動員2號開始準備
運動員1號開始準備
運動員4號開始準備
運動員0號準備完畢!在起跑線等待發(fā)令槍
運動員1號準備完畢酒甸!在起跑線等待發(fā)令槍
運動員2號準備完畢魄健!在起跑線等待發(fā)令槍
運動員3號準備完畢!在起跑線等待發(fā)令槍
運動員4號準備完畢插勤!在起跑線等待發(fā)令槍
Exception in thread "Thread-4" java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:250)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at thread.MyThread.run(MyThread.java:27)
運動員0號看不見起跑線了
運動員0號退場沽瘦!
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:250)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at thread.MyThread.run(MyThread.java:27)
運動員3號看不見起跑線了
運動員3號退場革骨!
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:250)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at thread.MyThread.run(MyThread.java:27)
運動員1號看不見起跑線了
運動員1號退場!
java.lang.NullPointerException
at thread.Test$1.run(Test.java:15)
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:220)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at thread.MyThread.run(MyThread.java:27)
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:250)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
at thread.MyThread.run(MyThread.java:27)
運動員2號看不見起跑線了
運動員2號退場其垄!
可以看到苛蒲,如果在執(zhí)行屏障動作的過程中出現(xiàn)異常,那么所有的線程都會拋出BrokenBarrierException異常绿满。
4.如果超出指定的等待時間臂外,當前線程會拋出 TimeoutException 異常,其他線程會拋出BrokenBarrierException異常喇颁。
package thread;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class MyThread extends Thread {
private CyclicBarrier cyclicBarrier;
private String name;
private int ID;
public MyThread(CyclicBarrier cyclicBarrier, String name, int ID) {
super();
this.cyclicBarrier = cyclicBarrier;
this.name = name;
this.ID = ID;
}
@Override
public void run() {
System.out.println(name + "開始準備");
try {
Thread.sleep(ID * 1000);
System.out.println(name + "準備完畢漏健!在起跑線等待發(fā)令槍");
try {
try {
cyclicBarrier.await(ID * 1000, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(name + "跑完了路程!");
} catch (BrokenBarrierException e) {
e.printStackTrace();
System.out.println(name + "看不見起跑線了");
}
System.out.println(name + "退場橘霎!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
輸出:
運動員0號開始準備
運動員2號開始準備
運動員3號開始準備
運動員1號開始準備
運動員0號準備完畢蔫浆!在起跑線等待發(fā)令槍
運動員4號開始準備
java.util.concurrent.TimeoutException運動員0號跑完了路程!
運動員0號退場姐叁!
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:257)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:435)
at thread.MyThread.run(MyThread.java:29)
運動員1號準備完畢瓦盛!在起跑線等待發(fā)令槍
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:207)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:435)
at thread.MyThread.run(MyThread.java:29)
運動員1號看不見起跑線了
運動員1號退場!
運動員2號準備完畢外潜!在起跑線等待發(fā)令槍
java.util.concurrent.BrokenBarrierException
運動員2號看不見起跑線了
運動員2號退場原环!
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:207)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:435)
at thread.MyThread.run(MyThread.java:29)
運動員3號準備完畢!在起跑線等待發(fā)令槍
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:207)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:435)
at thread.MyThread.run(MyThread.java:29)
運動員3號看不見起跑線了
運動員3號退場处窥!
運動員4號準備完畢嘱吗!在起跑線等待發(fā)令槍
java.util.concurrent.BrokenBarrierException
運動員4號看不見起跑線了
運動員4號退場!
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:207)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:435)
at thread.MyThread.run(MyThread.java:29)
從輸出可以看到滔驾,如果其中一個參與者拋出TimeoutException谒麦,其他參與者會拋出BrokenBarrierException。
與CountDownLatch不同的是哆致,CyclicBarrier是可以重復使用的绕德。并且CyclicBarrier可以傳遞一個action方法,是的所有線程到達后執(zhí)行某個任務沽瞭,這個是CountDownLatch不具備的迁匠。
新書Java并發(fā)編程系統(tǒng)與模型出版啦!比較通俗易懂驹溃,非常適合初學者,百度閱讀可以下載電子書延曙。