Java多線程中的CountDownLatch解析
一、概念簡介
CountDownLatch是一個(gè)阻塞部分線程直到其他線程執(zhí)行完成后喚醒的同步計(jì)數(shù)器
核心是其內(nèi)部類Sync繼承于AQS,同時(shí)也是利用的AQS的同步原理,也稱之為閉鎖。
二、使用場景
當(dāng)主線程進(jìn)行執(zhí)行時(shí),利用構(gòu)造方法初始化一個(gè)同步數(shù)state(AQS原理),主線程調(diào)用await方法進(jìn)行阻塞主線程即誰調(diào)用誰阻塞,其它線程調(diào)用countDown方法會(huì)對計(jì)數(shù)器減1直到0,會(huì)精準(zhǔn)喚醒被阻塞線程即被await方法阻塞的線程。
(1)用于多種數(shù)據(jù)源數(shù)據(jù)匯總;
(2)等待某一時(shí)間點(diǎn)才執(zhí)行邏輯如加載緩存、加載配置等;
注意:為了程序的健壯性,盡量給出合適的時(shí)間,防止子線程中斷導(dǎo)致線程無法喚醒的情況發(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)致無法喚醒主線程即無法達(dá)到屏障點(diǎn)。
CountDownLatch簡單使用
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)對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ì)列方法請轉(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í)則說明不需要阻塞 setHeadAndPropagate(node, r);//設(shè)置新的頭結(jié)點(diǎn)并釋放共享鎖 p.next = null; // help GC failed = false; return; } } /** * shouldParkAfterFailedAcquire主要是改變前節(jié)點(diǎn)的等待信號量 * parkAndCheckInterrupt在前者返回TRUE的情況下會(huì)直接調(diào)用LockSupport.park()進(jìn)行阻塞 * 上述兩種方法在AQS分析中可找到詳細(xì)解釋 */ if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) throw new InterruptedException();//上述兩個(gè)條件滿足則代表線程被中斷過 } } finally { if (failed)//出現(xiàn)異常且未執(zhí)行for循環(huán)中改變該failed值 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);//通過內(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類:
/** * 主要利用自旋鎖的原理,對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對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)為喚醒信號量 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è)置為無條件喚醒 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)對信號量節(jié)點(diǎn)即前繼節(jié)點(diǎn)等待值還原 * (2)對于node節(jié)點(diǎn)的后繼節(jié)點(diǎn)不為null直接喚醒或從后往前找尋信號量最靠前的線程進(jìn)行喚醒 */ private void unparkSuccessor(Node node) { int ws = node.waitStatus;//該節(jié)點(diǎn)等待狀態(tài)即頭結(jié)點(diǎn)的信號量 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)//從后往前查找最靠前的信號量node if (t.waitStatus <= 0)//信號量或初始化值 s = t; } if (s != null)//找到喚醒節(jié)點(diǎn) LockSupport.unpark(s.thread);對該線程進(jìn)行喚醒 }
到此這篇關(guān)于Java多線程中的CountDownLatch解析的文章就介紹到這了,更多相關(guān)CountDownLatch解析內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java基于API接口爬取商品數(shù)據(jù)的示例代碼
Java作為一種流行的編程語言,可以用于編寫程序來調(diào)用這些API接口,從而獲取商品數(shù)據(jù),本文將介紹如何使用Java基于API接口爬取商品數(shù)據(jù),包括請求API、解析JSON數(shù)據(jù)、存儲數(shù)據(jù)等步驟,并提供相應(yīng)的代碼示例,感興趣的朋友跟隨小編一起看看吧2023-10-10SpringBoot定時(shí)任務(wù)詳解與案例代碼
SpringBoot是一個(gè)流行的Java開發(fā)框架,它提供了許多便捷的特性來簡化開發(fā)過程,其中之一就是定時(shí)任務(wù)的支持,讓開發(fā)人員可以輕松地在應(yīng)用程序中執(zhí)行定時(shí)任務(wù),本文將詳細(xì)介紹如何在Spring?Boot中使用定時(shí)任務(wù),并提供相關(guān)的代碼示例2023-06-06Java class文件格式之方法_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了Java class文件格式之方法的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06java 中模擬UDP傳輸?shù)陌l(fā)送端和接收端實(shí)例詳解
這篇文章主要介紹了java 中模擬UDP傳輸?shù)陌l(fā)送端和接收端實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-03-03Jedis出現(xiàn)connection timeout問題解決方法(JedisPool連接池使用實(shí)例)
這篇文章主要介紹了Jedis出現(xiàn)connection timeout問題解決方法,使用Jedis的JedisPool連接池解決了這個(gè)問題,需要的朋友可以參考下2014-05-05spring.factories文件的解析源碼API機(jī)制詳解
通過本文深入探討Spring?Boot的背景歷史、業(yè)務(wù)場景、功能點(diǎn)以及底層原理,使讀者對Spring?Boot有了更深入的了解,結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-11-11解析Java編程中對于包結(jié)構(gòu)的命名和訪問
這篇文章主要介紹了Java編程中對于包結(jié)構(gòu)的命名和訪問,是Java入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下2015-12-12Java?spring注解@PostConstruct實(shí)戰(zhàn)案例講解
我們在Spring項(xiàng)目中經(jīng)常會(huì)遇到@PostConstruct注解,可能有的伙伴對這個(gè)注解很陌生,下面這篇文章主要給大家介紹了關(guān)于Java?spring注解@PostConstruct實(shí)戰(zhàn)案例講解的相關(guān)資料,需要的朋友可以參考下2023-12-12