CountDownLatch 初识
前言
在 JDK 并發包提供了幾個非常有用的并發工具類。CountDownLatch,CyclicBarrier 和 Semaphore 工具類提供了一種并發流程控制手段,Exchanger 工具類則提供了在線程間交換數據的一種手段。這篇文章主要了解 CountDownLatch 和 CyclicBarrier。
需求
假如有這樣一個需求:我們需要解析一個 Excel 里面的多個 sheet 頁的數據,此時可以考慮使用多線程,每個線程解析一個 sheet 頁,等到所有的的 sheet 頁都解析完之后,程序需要提示解析完成。在這個需求中,要實現主線程等待所有解析線程完成 sheet 的解析操作有兩種方式,第一種方式是 join,第二種方式是 CountDownLatch。
join
public class JoinTest {public static void main(String[] args) throws InterruptedException {Thread parserThread01 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("sheet1 parse finished!");}});Thread parserThread02 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("sheet2 parse finished!");}});parserThread01.start();parserThread02.start();parserThread01.join();parserThread02.join();System.out.println("all sheet parse finished!");} }運行結果:
sheet2 parse finished! sheet1 parse finished! all sheet parse finished!main 線程執行了 parserThread01.start(); 和 parserThread02.start(); 效果就是 main 線程會一直等待 parserThread01 和 parserThread02 執行完 才會繼續執行。
如果我需要 sheet2 的解析在 sheet1 之后進行,可以這樣修改。
public class JoinTest2 {public static void main(String[] args) throws InterruptedException {Thread parserThread01 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("sheet1 parse finished!");}});Thread parserThread02 = new Thread(new Runnable() {@Overridepublic void run() {try {parserThread01.join(); // *System.out.println("sheet2 parse finished!");} catch (InterruptedException e) {e.printStackTrace();}}});parserThread01.start();parserThread02.start();parserThread01.join();parserThread02.join();System.out.println("all sheet parse finished!");} }運行結果:
sheet1 parse finished! sheet2 parse finished! all sheet parse finished!解析順序是這樣的:main 線程要等待 parserThread01 和 parserThread02 解析完之后才開始執行 System.out.println("all sheet parse finished!");,而 parserThread02 要等待 parserThread01 解析完之后才開始解析。
join 的原理是不停的檢查 join 線程是否存活,如果 join 線程存活則讓當前線程永遠等待。其中,wait(0) 表示永遠等待下去。源碼片段如下:
while (isAlive()) {wait(0); }所以,接下來我們看一下,如果 parserThread02 線程一直在執行,main 線程會一直等待嗎?
public class JoinTest3 {public static void main(String[] args) throws InterruptedException {Thread parserThread01 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("sheet1 parse finished!");}});Thread parserThread02 = new Thread(new Runnable() {@Overridepublic void run() {try {parserThread01.join();System.out.println("sheet2 parse started!");for(;;); // *} catch (InterruptedException e) {e.printStackTrace();}}});parserThread01.setName("parserThread01");parserThread02.setName("parserThread02");parserThread01.start();parserThread02.start();parserThread01.join();parserThread02.join();System.out.println("all sheet parse finished!");} }運行結果:
從編輯器的左邊的紅點可以看出來程序一直沒結束,我們再來看一下線程的堆棧信息:
Main 線程
ParserThread02 線程
可以看到 parserThread02 線程一直處于 RUNNABLE 狀態,說明一直在運行中,而 Main 線程一直處于 WAITING 狀態,說明 Main 線程的確是一直等待 parserThread02 線程的。不過 JDK 提供了一個 join(long millis) 方法,拿這個案例來說,如果 Main 線程等待時間超過了 millis,Main 線程就會被喚醒,從WAITING 狀態變為RUNNABLE狀態,從而繼續執行。
小貼士:
這里可能有一個點大家比較困惑:Main 線程執行了 parserThread02.join() 方法之后從 RUNNABLE 狀態變為 WAITING 狀態,我們知道 join() 方法其實 join() 方法也是通過 wait() 方法來實現的,那么就有一個問題了:當 parserThread02 線程執行完畢之后,Main 線程從 WAITING 狀態變為 RUNNABLE 狀態,說白了也就是 Main 線程被喚醒,這個操作應該是要調用 notify() 方法的,那么這個 notify() 方法是在哪里被調用的?
解答: notify() 方法的調用實在 JVM 里面實現的,在 JDK 里面看不到,如果大家想詳細了解,可以看 JVM 源碼實現。
到這里為了解決這個需求,我們采取的都是 join() 的方式,那么有沒有別的方式呢?下面就給大家介紹另一種方式,也是我們本篇文章的重點:CountDownLatch。
CountDownLatch
代碼示例:
public class CountDownLatchTest {public static void main(String[] args) throws InterruptedException {CountDownLatch countDownLatch = new CountDownLatch(2);Thread parserThread01 = new Thread(new Runnable() {@Overridepublic void run() {try {System.out.println("sheet1 parse started,the time is: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));Thread.sleep(1000);countDownLatch.countDown();System.out.println("sheet1 parse finished,the countDownLatch count is: " + countDownLatch.getCount() +",the time is: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));} catch (InterruptedException e) {e.printStackTrace();}}});Thread parserThread02 = new Thread(new Runnable() {@Overridepublic void run() {try {System.out.println("sheet2 parse started,the time is: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));Thread.sleep(2000);countDownLatch.countDown();System.out.println("sheet2 parse finished,the countDownLatch count is: " + countDownLatch.getCount() +",the time is: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));} catch (InterruptedException e) {e.printStackTrace();}}});parserThread01.setName("parserThread01");parserThread02.setName("parserThread02");parserThread01.start();parserThread02.start();countDownLatch.await();System.out.println("all sheets parse finished,the countDownLatch count is: " + countDownLatch.getCount()+ " ,the time is: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));} }運行結果:
sheet1 parse started,the time is: 2019-09-28 20:20:59 sheet2 parse started,the time is: 2019-09-28 20:20:59 sheet1 parse finished,the countDownLatch count is: 1,the time is: 2019-09-28 20:21:00 sheet2 parse finished,the countDownLatch count is: 0,the time is: 2019-09-28 20:21:01 all sheet parse finished,the countDownLatch count is: 0 ,the time is: 2019-09-28 20:21:01從運行結果可以看到:Main 線程執行了 countDownLatch.await() 方法之后一直處于阻塞狀態,直到 parserThread02 線程執行完 countDownLatch.countDown(); 之后 Main 線程被喚醒。
每調用一次 countDown() 方法計數器都會減 1,直到計數器的值減為 0 時就代表條件已成熟,所有因調用 await() 方法而阻塞的線程都會被喚醒(前面說了喚醒操作是 JVM 調用的 notifyAll() 方法)。這就是 CountDownLatch 的內部機制,看起來很簡單,無非就是阻塞一部分線程讓其在達到某個條件之后再喚醒被阻塞的線程。但是 CountDownLatch 的應用場景卻比較廣泛。最常見的一個應用場景是開啟多個線程同時執行某個任務,等到所有任務都執行完再統計匯總結果。
轉載于:https://www.cnblogs.com/tkzL/p/11604750.html
總結
以上是生活随笔為你收集整理的CountDownLatch 初识的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 排查 Linux 系统故障,看这一篇足够
- 下一篇: 怎么在一里以外识别出一个菜鸡程序员