Java多線程中的CountDownLatch解析
一、概念簡(jiǎn)介
CountDownLatch是一個(gè)阻塞部分線程直到其他線程執(zhí)行完成后喚醒的同步計(jì)數(shù)器
核心是其內(nèi)部類Sync繼承于AQS,同時(shí)也是利用的AQS的同步原理,也稱之為閉鎖。
二、使用場(chǎng)景
當(dāng)主線程進(jìn)行執(zhí)行時(shí),利用構(gòu)造方法初始化一個(gè)同步數(shù)state(AQS原理),主線程調(diào)用await方法進(jìn)行阻塞主線程即誰(shuí)調(diào)用誰(shuí)阻塞,其它線程調(diào)用countDown方法會(huì)對(duì)計(jì)數(shù)器減1直到0,會(huì)精準(zhǔn)喚醒被阻塞線程即被await方法阻塞的線程。
(1)用于多種數(shù)據(jù)源數(shù)據(jù)匯總;
(2)等待某一時(shí)間點(diǎn)才執(zhí)行邏輯如加載緩存、加載配置等;
注意:為了程序的健壯性,盡量給出合適的時(shí)間,防止子線程中斷導(dǎo)致線程無(wú)法喚醒的情況發(fā)生。
三、特點(diǎn)
(1)子線程調(diào)用countDown方法只會(huì)減1,不會(huì)阻塞線程;
(2)主線程調(diào)用await方法會(huì)導(dǎo)致其被阻塞,當(dāng)計(jì)數(shù)器state被其他線程調(diào)用countDown方法減至0會(huì)喚醒被阻塞的線程;
(3)當(dāng)主線程發(fā)生中斷會(huì)拋出異常,導(dǎo)致無(wú)法喚醒主線程即無(wú)法達(dá)到屏障點(diǎn)。
CountDownLatch簡(jiǎn)單使用
public static void main(String[] args) {
System.out.println("main 線程開始執(zhí)行!");
CountDownLatch latch = new CountDownLatch(5);//初始化同步數(shù)
for (int i = 0; i < 5; i++) {
int threadId = i+1;
new Thread(()->{
System.out.println("線程"+threadId+"執(zhí)行!");
latch.countDown();
}).start();//java8 lamda表達(dá)式
}
System.out.println("即將被阻塞!");
try {
latch.await();//阻塞主線程,等待子線程將state減至0被喚醒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main 線程執(zhí)行完畢!");
}
四、CountDownLatch源碼分析
(1)構(gòu)造函數(shù)
/**
* CountDownLatch唯一的構(gòu)造函數(shù),實(shí)例化時(shí)只能使用指定同步數(shù)的構(gòu)造方法
*/
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);//利用內(nèi)部類(繼承AQS)對(duì)state進(jìn)行設(shè)置初始化大小
}
(2)await方法(核心)
CountDownLatch類:
public void await() throws InterruptedException {
//核心成員變量sync調(diào)用AQS中的方法acquireSharedInterruptibly
sync.acquireSharedInterruptibly(1);
}
AQS類:
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())//判斷是否有中斷標(biāo)志
throw new InterruptedException();
/**
* 該方法是由子類重寫,AQS強(qiáng)制其子類重寫,否則報(bào)錯(cuò)
* 根據(jù)if中的值判斷是否需要阻塞操作 1代表不需要阻塞 -1代表需要阻塞
*/
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);//調(diào)用AQS共享鎖阻塞操作
}
Sync類:
/**
* 獲取同步數(shù)并判斷是否需要喚醒
* 同步數(shù)state為0,則需要喚醒返回1即不需要阻塞
* 同步數(shù)state不為1,則不需要喚醒,返回-1后的操作即阻塞
*/
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;//獲取AQS中的state進(jìn)行返回是否需要進(jìn)行阻塞操作
}
//以共享鎖的方式進(jìn)行阻塞
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
/**
* addWaiter方法主要是基于當(dāng)前線程創(chuàng)建一個(gè)等待著并入隊(duì)且會(huì)創(chuàng)建一個(gè)哨兵節(jié)點(diǎn)
* addWaiter具體細(xì)節(jié)和其內(nèi)部enq初始化隊(duì)列方法請(qǐng)轉(zhuǎn)入AQS分析
*/
final Node node = addWaiter(Node.SHARED);//以共享鎖創(chuàng)建一個(gè)等待者node
boolean failed = true;
try {
for (;;) {//自旋,是否需要阻塞
final Node p = node.predecessor();//當(dāng)前線程的前繼節(jié)點(diǎn)
if (p == head) {//前繼節(jié)點(diǎn)是否為頭節(jié)點(diǎn)
int r = tryAcquireShared(arg);//嘗試獲取共享鎖即是否需要阻塞1和-1值
if (r >= 0) {//當(dāng)其大于等于時(shí),r值只能時(shí)1或者-1,滿足該條件時(shí)則說(shuō)明不需要阻塞
setHeadAndPropagate(node, r);//設(shè)置新的頭結(jié)點(diǎn)并釋放共享鎖
p.next = null; // help GC
failed = false;
return;
}
}
/**
* shouldParkAfterFailedAcquire主要是改變前節(jié)點(diǎn)的等待信號(hào)量
* parkAndCheckInterrupt在前者返回TRUE的情況下會(huì)直接調(diào)用LockSupport.park()進(jìn)行阻塞
* 上述兩種方法在AQS分析中可找到詳細(xì)解釋
*/
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();//上述兩個(gè)條件滿足則代表線程被中斷過(guò)
}
} finally {
if (failed)//出現(xiàn)異常且未執(zhí)行for循環(huán)中改變?cè)揻ailed值
cancelAcquire(node);//取消超時(shí)節(jié)點(diǎn)和當(dāng)前節(jié)點(diǎn)取消喚醒,AQS原理分析中詳細(xì)講解
}
}
(3)countDown方法(核心)
//用于子線程調(diào)用將同步數(shù)-1
public void countDown() {
sync.releaseShared(1);//通過(guò)內(nèi)部成員變量sync調(diào)用內(nèi)部Sync類繼承AQS中的釋放方法
}
AQS類:
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {//AQS類中定義強(qiáng)制子類重寫該方法,用于是否需要喚醒被阻塞的線程
doReleaseShared();//滿足判斷條件則進(jìn)行正常釋放
return true;//釋放成功
}
return false;//不需要釋放
}
Sync類:
/**
* 主要利用自旋鎖的原理,對(duì)state值進(jìn)行-1
*/
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();//獲取state值
if (c == 0)//還未開始自減,已為0則代表不能正常釋放
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))//CAS對(duì)state值進(jìn)行設(shè)置新的值
return nextc == 0;//計(jì)數(shù)器是否為0,此狀態(tài)為0代表可以正常釋放
}
}
/**
* 釋放共享鎖
*/
private void doReleaseShared() {
for (;;) {//自旋
Node h = head;//頭節(jié)點(diǎn)
if (h != null && h != tail) {//代表可喚醒且不是尾結(jié)點(diǎn)
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {//頭節(jié)點(diǎn)的等待狀態(tài)為喚醒信號(hào)量
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);//if中cas操作成功,則執(zhí)行該喚醒方法,否則進(jìn)行自旋或者結(jié)束
}else if (ws == 0 &&//初始化但未被改變時(shí)
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))//設(shè)置為無(wú)條件喚醒
continue;// loop on failed CAS 該else if中CAS失敗進(jìn)行自旋
}
if (h == head)//loop if head changed 循環(huán)判定頭結(jié)點(diǎn)是否發(fā)生變化,實(shí)際上是喚醒后會(huì)執(zhí)行這里結(jié)束自旋
break;
}
}
AQS喚醒共享鎖
/**
* (1)對(duì)信號(hào)量節(jié)點(diǎn)即前繼節(jié)點(diǎn)等待值還原
* (2)對(duì)于node節(jié)點(diǎn)的后繼節(jié)點(diǎn)不為null直接喚醒或從后往前找尋信號(hào)量最靠前的線程進(jìn)行喚醒
*/
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;//該節(jié)點(diǎn)等待狀態(tài)即頭結(jié)點(diǎn)的信號(hào)量
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);//將該節(jié)點(diǎn)的狀態(tài)值設(shè)置為0即初始值
Node s = node.next;//獲取喚醒節(jié)點(diǎn)即node的下一節(jié)點(diǎn)
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)//從后往前查找最靠前的信號(hào)量node
if (t.waitStatus <= 0)//信號(hào)量或初始化值
s = t;
}
if (s != null)//找到喚醒節(jié)點(diǎn)
LockSupport.unpark(s.thread);對(duì)該線程進(jìn)行喚醒
}
到此這篇關(guān)于Java多線程中的CountDownLatch解析的文章就介紹到這了,更多相關(guān)CountDownLatch解析內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot校驗(yàn)前端傳遞的參數(shù)的流程步驟
在日常項(xiàng)目開發(fā)中,我們都知道參數(shù)驗(yàn)證是必不可少的一環(huán),但是有時(shí)候?yàn)榱送祽?把參數(shù)校驗(yàn)交給前端開發(fā)人員去處理,這樣很容易影響系統(tǒng)穩(wěn)定性和安全性,所以本文給大家介紹了SpringBoot校驗(yàn)前端傳遞的參數(shù)的流程步驟,需要的朋友可以參考下2025-10-10
Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(24)
下面小編就為大家?guī)?lái)一篇Java基礎(chǔ)的幾道練習(xí)題(分享)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧,希望可以幫到你2021-07-07
使用Java獲取系統(tǒng)信息的常用代碼整理總結(jié)
這篇文章主要介紹了使用Java獲取系統(tǒng)信息的常用代碼整理總結(jié),在服務(wù)器端一般經(jīng)常能夠用到,歡迎收藏,需要的朋友可以參考下2015-11-11
Spring Boot實(shí)現(xiàn)文件上傳示例代碼
本篇文章主要介紹了Spring Boot實(shí)現(xiàn)文件上傳示例代碼,可以實(shí)現(xiàn)單文件和多文件的上傳,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-03-03

