Java高并發(fā)編程基礎(chǔ)三大利器之CountDownLatch
點擊上方“Java金融”,選擇“設(shè)為星標(biāo)”
后臺回復(fù)"888"獲取bat面試題集
引言
上一篇文章我們介紹了AQS的信號量Semaphore《Java高并發(fā)編程基礎(chǔ)三大利器之Semaphore》,接下來應(yīng)該輪到CountDownLatch了。
什么是CountDownLatch
CountDownLatch是通過一個計數(shù)器來實現(xiàn)的,計數(shù)器的初始值是線程的數(shù)量。每當(dāng)一個線程執(zhí)行完畢后,計數(shù)器的值就減1,當(dāng)計數(shù)器的值為0時,表示所有線程都執(zhí)行完畢,然后在閉鎖上(調(diào)用await方法的線程)等待的線程就可以恢復(fù)工作了。
應(yīng)用場景
CountDownLatch可以用來干什么呢?有什么應(yīng)用場景?實際項目中有應(yīng)用的場景嗎?這應(yīng)該才是大家比較關(guān)心的。我們先來看看官網(wǎng)提供的例子是如何進行應(yīng)用的https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CountDownLatch.html
官方提供了兩個demo我直接把它轉(zhuǎn)成了圖片順帶推薦下這個代碼轉(zhuǎn)圖片的網(wǎng)址https://www.dute.org/code-snapshot 還挺好用的。
官網(wǎng)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等待,直到所有的工人完成.說白了就是司機要等到所有工人完工為止。
官網(wǎng)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.
”
另一種典型的用法就是把一個大任務(wù)拆分N個部分,讓多個線程(Worker)執(zhí)行,每個線程(Worker)執(zhí)行完自己的部分計數(shù)器就減1,當(dāng)所有子部分都完成后,Driver 才繼續(xù)向下執(zhí)行才繼續(xù)執(zhí)行。就好比富士康手機加工的流水線一樣,組裝一步手機需要一條條的流水線來相互配合完成。一條條流水線(Worker),每條線都干自己的活。有的流水線是貼膜的,有的流水線是打螺絲的,有的流水線是質(zhì)檢的、有的流水線充電的、有的流水線貼膜的。等這些流水線都干完了就把一部手機組裝完成了。
上面兩個就是官方提供的demo,下面我再來兩個我們平時開發(fā)中可以用到的栗子:
多個線程等待:模擬并發(fā),讓并發(fā)線程一起執(zhí)行。
有時候我們寫了接口想去壓測下它,看看它的最大并發(fā)數(shù)大概是多少。當(dāng)然我們可以使用Jmeter來進行壓測,但是有時候我們不想去下載工具,其實就可以借助CountDownLatch來實現(xiàn)。
/**
* @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();
// 調(diào)用測試接口
System.out.println(Thread.currentThread().getName() + "開始執(zhí)行……");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
// 讓請求都準(zhǔn)備好
Thread.sleep(2000);
// 讓所有請求統(tǒng)一請求
countDownLatch.countDown();
}
}
我們通過CountDownLatch.await(),讓多個參與者線程啟動后阻塞等待,然后在主線程 調(diào)用CountDownLatch.countdown() 將計數(shù)減為0,讓所有線程一起往下執(zhí)行;以此實現(xiàn)了多個線程在同一時刻并發(fā)執(zhí)行,來模擬并發(fā)請求的目的。
單個線程等待:多個線程(任務(wù))完成后,進行匯總合并
/**
* @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();// 主線程在阻塞,當(dāng)計數(shù)器==0,就喚醒主線程往下執(zhí)行。
System.out.println("主線程:在所有任務(wù)運行完成后,進行結(jié)果匯總");
}
}
這種場景應(yīng)該是用的最多了,比如我們打開一個電商的個人中心頁面,我們需要調(diào)用,用戶信息接口、用戶訂單接口、用戶會員信息等接口,然后合并后一起給到前端,假設(shè)每個接口最長耗時為1s,如果我們同步調(diào)用的話最大耗時時間是3s,如果我們采用異步調(diào)用然后合并結(jié)果,所以最大的耗時時間是3s。每個接口調(diào)用返回數(shù)據(jù)后調(diào)用countDown方法,讓計數(shù)器進行減1,當(dāng)把計數(shù)器減為0時的這個線程會去喚醒主線程,讓它繼續(xù)往下走。
CountDownLatch 實現(xiàn)原理
CountDownLatch是通過AQS的state字段來實現(xiàn)的一個計數(shù)器,計數(shù)器的初始值(state的值)為new CountDownLatch設(shè)置的數(shù)量,每次調(diào)用countDown的時候,state的值會進行減1,最后某個線程將state值減為0時,會把調(diào)用了await()進行阻塞等待的線程進行喚醒。CountDownLatch重寫了tryReleaseShared這個方法,只有當(dāng)state這個字段被設(shè)置為0時,也就是tryReleaseShared返回true的情況就會執(zhí)行doReleaseShared方法,把調(diào)用了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高并發(fā)編程基礎(chǔ)之AQS》、《Java高并發(fā)編程基礎(chǔ)三大利器之Semaphore》再來看這個還是比較輕松的。
總結(jié)
CountDownLatch不能重新初始化或者修改CountDownLatch內(nèi)部計數(shù)器的值。CountDownLatch和Semaphore在使用AQS的方式上很相似,在同步狀態(tài)中都是保存的是當(dāng)前的計數(shù)值。CountDownLatch的作用就是允許一個或多個線程等待其他線程完成操作,看起來有點類似join()方法,但其提供了比join() 更加靈活的API。CountDownLatch可以手動控制在n個線程里調(diào)用n次countDown()方法使計數(shù)器進行減一操作,也可以在一個線程里調(diào)用n次執(zhí)行減一操作。join() 的實現(xiàn)原理是不停檢查join線程是否存活,如果join線程存活則讓當(dāng)前線程永遠(yuǎn)等待。所以兩者之間相對來說還是CountDownLatch使用起來較為靈活。
結(jié)束
由于自己才疏學(xué)淺,難免會有紕漏,假如你發(fā)現(xiàn)了錯誤的地方,還望留言給我指出來,我會對其加以修正。 如果你覺得文章還不錯,你的轉(zhuǎn)發(fā)、分享、贊賞、點贊、留言就是對我最大的鼓勵。 感謝您的閱讀,十分歡迎并感謝您的關(guān)注。
站在巨人的肩膀上摘蘋果:
https://javajr.cn/ https://zhuanlan.zhihu.com/p/148231820
往期精選
最近面試BAT,整理一份面試資料《Java面試BATJ通關(guān)手冊》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)、等等。獲取方式:點“在看”,關(guān)注公眾號并回復(fù) 666 領(lǐng)取,更多內(nèi)容陸續(xù)奉上。
文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。
謝謝支持喲 (*^__^*)
