Java高並發編程基礎三大利器之CountDownLatch
引言
上一篇文章我們介紹了AQS
的訊號量Semaphore
《Java高並發編程基礎三大利器之Semaphore》,接下來應該輪到CountDownLatch
了。
什麼是CountDownLatch
CountDownLatch
是通過一個計數器來實現的,計數器的初始值是執行緒的數量。每當一個執行緒執行完畢後,計數器的值就減1,當計數器的值為0
時,表示所有執行緒都執行完畢,然後在閉鎖上(調用await方法的執行緒)等待的執行緒就可以恢復工作了。
應用場景
CountDownLatch
可以用來幹什麼呢?有什麼應用場景?實際項目中有應用的場景嗎?這應該才是大家比較關心的。我們先來看看官網提供的例子是如何進行應用的//docs.oracle.com/javase/8/docs/api/java/util/concurrent/CountDownLatch.html
官方提供了兩個demo
我直接把它轉成了圖片順帶推薦下這個程式碼轉圖片的網址//www.dute.org/code-snapshot 還挺好用的。
官網demo1
The first is a start signal that prevents any worker from proceeding until the driver is ready for them to proceed;
The second is a completion signal that allows the driver to wait until all workers have completed.
- 第一個開始訊號(
startSignal
)會阻止任何工人(worker
)開始工作,在司機到來之前。說白了就是司機沒來工人就不能幹活。 - 第二個是完成訊號 (
doneSignal
),允許司機Driver
等待,直到所有的工人完成.說白了就是司機要等到所有工人完工為止。
官網demo2
Another typical usage would be to divide a problem into N parts, describe each part with a Runnable that executes that portion and counts down on the latch, and queue all the Runnables to an Executor. When all sub-parts are complete, the coordinating thread will be able to pass through await.
另一種典型的用法就是把一個大任務拆分N個部分,讓多個執行緒(Worker)執行,每個執行緒(Worker)執行完自己的部分計數器就減1,當所有子部分都完成後,Driver 才繼續向下執行才繼續執行。
就好比富士康手機加工的流水線一樣,組裝一步手機需要一條條的流水線來相互配合完成。一條條流水線(Worker),每條線都干自己的活。有的流水線是貼膜的,有的流水線是打螺絲的,有的流水線是質檢的、有的流水線充電的、有的流水線貼膜的。等這些流水線都幹完了就把一部手機組裝完成了。
上面兩個就是官方提供的demo,下面我再來兩個我們平時開發中可以用到的栗子:
多個執行緒等待:模擬並發,讓並發執行緒一起執行。
有時候我們寫了介面想去壓測下它,看看它的最大並發數大概是多少。當然我們可以使用Jmeter
來進行壓測,但是有時候我們不想去下載工具,其實就可以藉助CountDownLatch
來實現。
/**
* @author: 公眾號:java金融
*/
public class TestCountDownLatch1 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
//所有請求都阻塞在這,等待
countDownLatch.await();
// 調用測試介面
System.out.println(Thread.currentThread().getName() + "開始執行……");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
// 讓請求都準備好
Thread.sleep(2000);
// 讓所有請求統一請求
countDownLatch.countDown();
}
}
我們通過CountDownLatch.await()
,讓多個參與者執行緒啟動後阻塞等待,然後在主執行緒 調用CountDownLatch.countdown()
將計數減為0
,讓所有執行緒一起往下執行;以此實現了多個執行緒在同一時刻並發執行,來模擬並發請求的目的。
單個執行緒等待:多個執行緒(任務)完成後,進行匯總合併
/**
* @author: 公眾號:java金融
*/
public class TestCountDownLatch1 {
public static void main(String[] args) throws InterruptedException {
int count = 3;
CountDownLatch countDownLatch = new CountDownLatch(count);
for (int i = 0; i < count; i++) {
final int index = i;
new Thread(() -> {
try {
Thread.sleep(1000 + ThreadLocalRandom.current().nextInt(1000));
System.out.println("finish" + index + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();// 主執行緒在阻塞,當計數器==0,就喚醒主執行緒往下執行。
System.out.println("主執行緒:在所有任務運行完成後,進行結果匯總");
}
}
這種場景應該是用的最多了,比如我們打開一個電商的個人中心頁面,我們需要調用,用戶資訊介面、用戶訂單介面、用戶會員資訊等介面,然後合併後一起給到前端,假設每個介面最長耗時為1s
,如果我們同步調用的話最大耗時時間是3s,如果我們採用非同步調用然後合併結果,所以最大的耗時時間是3s
。每個介面調用返回數據後調用countDown
方法,讓計數器進行減1,當把計數器減為0時的這個執行緒會去喚醒主執行緒,讓它繼續往下走。
CountDownLatch 實現原理
CountDownLatch
是通過AQS
的state
欄位來實現的一個計數器,計數器的初始值(state
的值)為new CountDownLatch
設置的數量,每次調用countDown
的時候,state的值會進行減1,最後某個執行緒將state值減為0時,會把調用了await()
進行阻塞等待的執行緒進行喚醒。CountDownLatch
重寫了tryReleaseShared
這個方法,只有當state
這個欄位被設置為0時,也就是tryReleaseShared
返回true
的情況就會執行doReleaseShared
方法,把調用了await的執行緒進行喚醒。
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
CountDownLatch
的其他源碼就不進行分析了,
相信看了這兩篇文章《Java高並發編程基礎之AQS》、《Java高並發編程基礎三大利器之Semaphore》再來看這個還是比較輕鬆的。
總結
CountDownLatch
不能重新初始化或者修改CountDownLatch
內部計數器的值。CountDownLatch
和Semaphore
在使用AQS
的方式上很相似,在同步狀態中都是保存的是當前的計數值。CountDownLatch
的作用就是允許一個或多個執行緒等待其他執行緒完成操作,看起來有點類似join()
方法,但其提供了比join
() 更加靈活的API。CountDownLatch
可以手動控制在n個執行緒里調用n
次countDown
()方法使計數器進行減一操作,也可以在一個執行緒里調用n次執行減一操作。join
() 的實現原理是不停檢查join執行緒是否存活,如果join
執行緒存活則讓當前執行緒永遠等待。所以兩者之間相對來說還是CountDownLatch使用起來較為靈活。
結束
- 由於自己才疏學淺,難免會有紕漏,假如你發現了錯誤的地方,還望留言給我指出來,我會對其加以修正。
- 如果你覺得文章還不錯,你的轉發、分享、讚賞、點贊、留言就是對我最大的鼓勵。
- 感謝您的閱讀,十分歡迎並感謝您的關注。
站在巨人的肩膀上摘蘋果:
//javajr.cn/
//zhuanlan.zhihu.com/p/148231820