天天看點

java線程栅欄_Java多線程并發系列之閉鎖(Latch)和栅欄(CyclicBarrier)

今天項目上遇到一個多線程任務問題,大概圖文描述一下:

1.前端需要及時傳回任務狀态

2.背景開了一個任務線程去執行具體的業務,業務包括四個部分,四個部分全部完成才算完成

3.業務中某些耗時的或者需要多線程的任務單獨又開了一個線程池

4.由于任務主線程和子任務線程池是并行運作,如何知道整個業務線程什麼時候會完成?

java線程栅欄_Java多線程并發系列之閉鎖(Latch)和栅欄(CyclicBarrier)

好的,筆者起初的思想是給業務線程的每個子任務加了一個标記,另外起了一個監聽線程在任務啟動時開始監聽所有業務标記是否已經達到完成狀态,如果全部子任務标記完成,則認為任務完成。

業務實作上是完全沒有問題的。隻是單獨開線程這個操作還是有點騷,并且得維護子任務标記。屬實不太友善

這裡最近看多線程時看到了門栓的用法,瞬間感覺可以應用一波套路

大緻的實作就變成了對子任務線程池中的任務進行門栓鎖定,當門栓開了後方可進行後續的任務進行,當最後子一個任務完成時,任務即可完成。

是不是友善很多,省了大把業務标記和任務監聽線程。

好的 那麼剩下的篇幅我們就來了解一下門栓的一個基本用法

1. 閉鎖(Latch)

閉鎖(Latch)  —— 確定多個線程在完成各自事務後,才會打開繼續執行後面的内容,否則一直等待。

計數器閉鎖(CountDownLatch) —— 是JDK5+ 裡面閉鎖的一個實作,允許一個或多個線程等待某個事件的發生。CountDownLatch  有個正數的計數器,countDown();對計數器做減法操作,await(); 等待計數器 = 0。所有await的線程都會阻塞,直到計數器為0或者等待線程中斷或者逾時。

主要是兩個方法:CountDown  和 Await方法分别為門栓減鎖和門栓等待。

public static void main(String[] args) throws InterruptedException {

// 申明,等待事件數量 5次

CountDownLatch await = new CountDownLatch(5);

// 依次建立并啟動處于等待狀态的5個MyRunnable線程

for (int i = 1; i < 6; ++i) {

new Thread(new MyRunnable(await, i)).start();

}

System.out.println("等待線程開始工作......");

await.await();

System.out.println("結束!");

}

流程說明

java線程栅欄_Java多線程并發系列之閉鎖(Latch)和栅欄(CyclicBarrier)

這裡順便了解一下栅欄,與門栓類似的一個操作,雖然暫時未遇到這樣的業務場景

2.栅欄(CyclicBarrier)

栅欄類似于閉鎖,它能阻塞一組線程直到某個事件發生。 栅欄與閉鎖的關鍵差別在于,所有的線程必須同時到達栅欄位置,才能繼續執行。閉鎖用于等待事件,而栅欄用于等待其他線程。

大緻意思:某個業務需要分幾個線程去做,但某些子任務線程中某個細節需要等待其他任務都完成到指定的栅欄位置後才可繼續往後執行。下面這個例子摘自網上 雖然也不是很形象 可細看流程圖

場景: 比如甲乙丙三人一把椅子,甲做椅子腿,乙做椅子面,丙做椅子靠背。等3人都做成後,就可以組裝成椅子了。這是一種并行疊代,将一個問題分成很多子問題,當一系列的子問題都解決之後(所有子問題線程都已經await(); ),此時将栅欄打開,所有子問題線程被釋放,而栅欄位置可以留着下次使用。

示例如下:

public static void main(String[] args) throws InterruptedException {

// 申明,等待線程數量 3次

CyclicBarrier cyclicBarrier = new CyclicBarrier(3);

// 依次建立并啟動處于等待狀态的3個MyRunnable2線程

new Thread(new ChairRunnable(cyclicBarrier, "椅子腿")).start();

new Thread(new ChairRunnable(cyclicBarrier, "椅子面")).start();

new Thread(new ChairRunnable(cyclicBarrier, "椅子背")).start();

}

public static class ChairRunnable implements Runnable {

private final CyclicBarrier cyclicBarrier;

private final String event;

public ChairRunnable(CyclicBarrier cyclicBarrier, String event) {

this.cyclicBarrier = cyclicBarrier;

this.event = event;

}

public void run() {

try {

System.out.println("開始做【" + event + "】。");

Thread.sleep(new Random().nextInt(10000));

cyclicBarrier.await(); // 等待其他線程完成

} catch (InterruptedException e) {

e.printStackTrace();

} catch (BrokenBarrierException e) {

e.printStackTrace();

}

System.out.println("【" + event + "】做好了, 我們來一起組裝吧!");

}

}

運作結果:

開始做【椅子腿】。

開始做【椅子背】。

開始做【椅子面】。

【椅子面】做好了, 我們來一起組裝吧!

【椅子腿】做好了, 我們來一起組裝吧!

【椅子背】做好了, 我們來一起組裝吧!

流程如下:

java線程栅欄_Java多線程并發系列之閉鎖(Latch)和栅欄(CyclicBarrier)