前言
JDK中為了處理執行緒之間的同步問題,除了提供鎖機制之外,還提供了幾個非常有用的併發工具類:CountDownLatch、CyclicBarrier、Semphore、Exchanger、Phaser;
CountDownLatch、CyclicBarrier、Semphore、Phaser 這四個工具類提供一種併發流程的控制手段;而Exchanger工具類則提供了線上程之間交換資料的一種手段。
簡介
CyclicBarrier 的字面意思是可迴圈使用(Cyclic)的屏障(Barrier)。它要做的事情是,讓一組執行緒到達一個屏障(也可以叫同步點)時被阻塞,直到最後一個執行緒到達屏障時,屏障才會開門,所有被屏障攔截的執行緒才會繼續幹活。CyclicBarrier預設的構造方法是CyclicBarrier(int parties),其參數列示屏障攔截的執行緒數量,每個執行緒呼叫await方法告訴CyclicBarrier我已經到達了屏障,然後當前執行緒被阻塞。
構造方法摘要
方法名稱 | 說明 |
---|---|
CyclicBarrier(int parties) | 建立一個新的 CyclicBarrier,它將在給定數量的參與者(執行緒)處於等待狀態時啟動,但它不會在啟動 barrier 時執行預定義的操作。 |
CyclicBarrier(int parties, Runnable barrierAction) | 建立一個新的 CyclicBarrier,它將在給定數量的參與者(執行緒)處於等待狀態時啟動,並在啟動 barrier 時執行給定的屏障操作,該操作由最後一個進入 barrier 的執行緒執行。 |
方法摘要
方法名稱 | 說明 |
---|---|
public int await() throws InterruptedException, BrokenBarrierException | 在所有參與者都已經在此 barrier 上呼叫 await 方法之前,將一直等待。 返回: 到達的當前執行緒的索引,其中,索引 getParties() - 1 指示將到達的第一個執行緒,零指示最後一個到達的執行緒. |
public int await(long timeout,TimeUnit unit) throws InterruptedException,BrokenBarrierException, ITimeoutException | 在所有參與者都已經在此屏障上呼叫 await 方法之前將一直等待,或者超出了指定的等待時間。 |
public void reset() | 將屏障重置為其初始狀態。如果所有參與者目前都在屏障處等待,則它們將返回,同時丟擲一個 BrokenBarrierException。注意,在由於其他原因造成損壞之後,實行重置可能會變得很複雜; |
public boolean isBroken() | 查詢此屏障是否處於損壞狀態。 |
public int getNumberWaiting() | 返回當前在屏障處等待的參與者數目。此方法主要用於除錯和斷言。 |
public int getParties() | 返回要求啟動此 barrier 的參與者數目。 |
注意:
- 對於失敗的同步嘗試,CyclicBarrier 使用了一種要麼全部要麼全不 (all-or-none) 的破壞模式:如果因為中斷、失敗或者超時等原因,導致執行緒過早地離開了屏障點,那麼在該屏障點等待的其他所有執行緒也將通過 BrokenBarrierException(如果它們幾乎同時被中斷,則用 InterruptedException)以反常的方式離開。
- 記憶體一致性效果:執行緒中呼叫 await() 之前的操作 happen-before 那些是屏障操作的一部份的操作,後者依次 happen-before 緊跟在從另一個執行緒中對應 await() 成功返回的操作。
@ Example1 屏障操作的例子
public static void main(String[] args) {
//設定5個屏障,並且有屏障操作
CyclicBarrier barrier = new CyclicBarrier(5,new Runnable() {
@Override
public void run() {
System.out.println("執行緒"+Thread.currentThread().getName()+"執行了屏障操作");
}
});
for(int i=0;i<5;i++){
//建立5個執行緒
Thread thread = new Thread(new MyRunable(barrier),"thread_"+i);
thread.start();
}
}
複製程式碼
class MyRunable implements Runnable{
CyclicBarrier barrier;
public MyRunable(CyclicBarrier barrier ){
this.barrier = barrier;
}
@Override
public void run() {
//一系列操作...
System.out.println("執行緒 "+Thread.currentThread().getName()+" 到達了屏障點!");
try {
int index = barrier.await();
if(index== (barrier.getParties()-1)){
//第一個到達屏障點的執行緒,執行特殊操作....
System.out.println("所有執行緒到達屏障點,執行緒 "+Thread.currentThread().getName()+" 被喚醒!!此執行緒是第一個到達屏障點");
}else if(index == 0){//最後一個到達屏障點的執行緒
System.out.println("所有執行緒到達屏障點,執行緒 "+Thread.currentThread().getName()+" 被喚醒!!此執行緒是最後一個到達屏障點");
}else{
System.out.println("所有執行緒到達屏障點,執行緒 "+Thread.currentThread().getName()+" 被喚醒!!");
}
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}
複製程式碼
執行結果:
執行緒 thread_1 到達了屏障點!
執行緒 thread_4 到達了屏障點!
執行緒 thread_3 到達了屏障點!
執行緒 thread_0 到達了屏障點!
執行緒 thread_2 到達了屏障點!
執行緒thread_3執行了屏障操作
所有執行緒到達屏障點,執行緒 thread_3 被喚醒!!此執行緒是最後一個到達屏障點
所有執行緒到達屏障點,執行緒 thread_0 被喚醒!!
所有執行緒到達屏障點,執行緒 thread_4 被喚醒!!
所有執行緒到達屏障點,執行緒 thread_1 被喚醒!!此執行緒是第一個到達屏障點
所有執行緒到達屏障點,執行緒 thread_2 被喚醒!!
複製程式碼
上面的例子,使用了傳入屏障操作的Runable引數的構造方法,
。然而,在實際使用中,
,如上面的例子,第一個和最後一個到達屏障點的執行緒都執行特殊的操作。
順便說一下,可能會對本例子中前5個輸出的順序 有所疑惑:thread_3 通過awiat()方法返回的索引值,可知 thread_3 是最後一個到達屏障點的,但為什麼輸出的順序卻是第三個,而不是最後一個;在這就要真正理解CyclicBarrier,CyclicBarrier 本質上是一把鎖,多個執行緒在使用CyclicBarrier 物件時,是需要先獲取鎖,即需要互斥訪問,所以呼叫await( )方法不一定能夠馬上獲取鎖。上面的例子,是先列印輸出,再去獲取鎖,所以輸出順序不是到達屏障點的順序。
@ Example2 應用場景
下面的例子是:CyclicBarrier用於多執行緒計算資料,最後合併計算結果的場景。比如我們用一個Excel儲存了使用者所有銀行流水,每個Sheet儲存一個帳戶近一年的每筆銀行流水,現在需要統計使用者的日均銀行流水,先用多執行緒處理每個sheet裡的銀行流水,都執行完之後,得到每個sheet的日均銀行流水,最後,再用barrierAction用這些執行緒的計算結果,計算出整個Excel的日均銀行流水。
public class BankWaterService implements Runnable {
//建立4個屏障,處理完後執行當前類的run方法
private CyclicBarrier barrier = new CyclicBarrier(4,this);
//假設只有4個sheet,所以只啟動4個執行緒
private Executor excutor = Executors.newFixedThreadPool(4);
//儲存每個sheet計算出的結果
private ConcurrentHashMap< String, Integer> sheetBankWaterCount = new ConcurrentHashMap<>();
private void count(){
for(int i=0;i<4;i++){
excutor.execute(new Runnable() {
@Override
public void run() {
//計算過程.....
//儲存計算結果
sheetBankWaterCount.put(Thread.currentThread().getName(), 1);
try {
//計算完成,插入屏障
barrier.await();
//後續操作,將會使用到四個執行緒的執行結果....
System.out.println("執行緒"+Thread.currentThread().getName()+"執行結束,最終的計算結果:"+sheetBankWaterCount.get("result"));
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
});
}
}
@Override
public void run() {
int result = 0;
for(Entry<String, Integer> item : sheetBankWaterCount.entrySet()){
result += item.getValue();
}
sheetBankWaterCount.put("result", result);
}
public static void main(String[] args) {
BankWaterService bankWaterService = new BankWaterService();
bankWaterService.count();
}
}
複製程式碼
執行結果:
執行緒pool-1-thread-4執行結束,最終的計算結果:4
執行緒pool-1-thread-2執行結束,最終的計算結果:4
執行緒pool-1-thread-1執行結束,最終的計算結果:4
執行緒pool-1-thread-3執行結束,最終的計算結果:4
複製程式碼
CyclicBarrier和CountDownLatch的區別
- CountDownLatch: 一個執行緒(或者多個執行緒), 等待另外N個執行緒完成某個事情之後才能執行。而這N個執行緒通過呼叫CountDownLatch.countDown()方法 來告知“某件事件”完成,即計數減一。而一個執行緒(或者多個執行緒)則通過CountDownLatch.awiat( ) 進入等待狀態,直到 CountDownLatch的計數為0時,才會全部被喚醒
- CyclicBarrier : N個執行緒相互等待,任何一個執行緒完成某個事情之前,所有的執行緒都必須等待。CountDownLatch 是計數器, 執行緒完成一個就記一個, 就像 報數一樣, 只不過是遞減的.
而CyclicBarrier更像一個水閘, 執行緒執行就想水流, 在水閘處都會堵住, 等到水滿(執行緒到齊)了, 才開始洩流. - CountDownLatch只能使用一次,CyclicBarrier則可以通過reset( )方法重置後,重新使用。所以CyclicBarrier可以用於更復雜的業務場景。例如:計算錯誤,可以重置計數器,並讓執行緒重新執行一次。
文章源地址:https://www.cnblogs.com/jinggod/p/8494193.html