Java AQS中閉鎖CountDownLatch的使用
一. 簡(jiǎn)介
CountDownLatch(閉鎖)是一個(gè)同步協(xié)助類,允許一個(gè)或多個(gè)線程等待,直到其他線程完成操作集。
CountDownLatch使用給定的計(jì)數(shù)值(count)初始化。await方法會(huì)阻塞直到當(dāng)前的計(jì)數(shù)值(count)由于countDown方法的調(diào)用達(dá)到0,count為0之后所有等待的線程都會(huì)被釋放,并且隨后對(duì)await方法的調(diào)用都會(huì)立即返回。這是一個(gè)一次性現(xiàn)象 —— count不會(huì)被重置。如果你需要一個(gè)重置count的版本,那么請(qǐng)考慮使用CyclicBarrier。
二. 使用
構(gòu)造器
public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); }
常用方法
// 調(diào)用 await() 方法的線程會(huì)被掛起,它會(huì)等待直到 count 值為 0 才繼續(xù)執(zhí)行 public void await() throws InterruptedException { }; // 和 await() 類似,若等待 timeout 時(shí)長(zhǎng)后,count 值還是沒有變?yōu)?0,不再等待,繼續(xù)執(zhí)行 public boolean await(long timeout, TimeUnit unit) throws InterruptedException { }; // 會(huì)將 count 減 1,直至為 0 public void countDown() { sync.releaseShared(1); }
三. 應(yīng)用場(chǎng)景
CountDownLatch一般用作多線程倒計(jì)時(shí)計(jì)數(shù)器,強(qiáng)制它們等待其他一組(CountDownLatch的初始化決定)任務(wù)執(zhí)行完成。
CountDownLatch的兩種使用場(chǎng)景:
- 讓多個(gè)線程等待
- 讓單個(gè)線程等待
場(chǎng)景1 讓多個(gè)線程等待:模擬并發(fā),讓并發(fā)線程一起執(zhí)行
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(); String parter = "【" + Thread.currentThread().getName() + "】"; System.out.println(parter + "開始執(zhí)行……"); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } Thread.sleep(2000); countDownLatch.countDown(); }
for循環(huán)中等待阻塞,直到執(zhí)行countdown方法。
場(chǎng)景2 讓單個(gè)線程等待:多個(gè)線程(任務(wù))完成后,進(jìn)行匯總合并。
很多時(shí)候,我們的并發(fā)任務(wù),存在前后依賴關(guān)系;比如數(shù)據(jù)詳情頁(yè)需要同時(shí)調(diào)用多個(gè)接口獲取數(shù)據(jù),并發(fā)請(qǐng)求獲取到數(shù)據(jù)后、需要進(jìn)行結(jié)果合并;或者多個(gè)數(shù)據(jù)操作完成后,需要數(shù)據(jù)check;這其實(shí)都是:在多個(gè)線程(任務(wù))完成后,進(jìn)行匯總合并的場(chǎng)景。
public static void main(String[] args) throws Exception { CountDownLatch countDownLatch = new CountDownLatch(5); for (int i = 0; i < 5; i++) { final int index = i; new Thread(() -> { try { Thread.sleep(1000 + ThreadLocalRandom.current().nextInt(1000)); System.out.println(Thread.currentThread().getName() + " finish task" + index); countDownLatch.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } // 主線程在阻塞,當(dāng)計(jì)數(shù)器==0,就喚醒主線程往下執(zhí)行。 countDownLatch.await(); System.out.println("主線程:在所有任務(wù)運(yùn)行完成后,進(jìn)行結(jié)果匯總"); }
四. 底層原理
底層基于 AbstractQueuedSynchronizer 實(shí)現(xiàn),CountDownLatch 構(gòu)造函數(shù)中指定的count直接賦給AQS的state;每次countDown()則都是release(1)減1,最后減到0時(shí)unpark線程;這一步是由最后一個(gè)執(zhí)行countdown方法的線程執(zhí)行的。
而調(diào)用await()方法時(shí),當(dāng)前線程就會(huì)判斷state屬性是否為0,如果為0,則繼續(xù)往下執(zhí)行,如果不為0,則使當(dāng)前線程進(jìn)入等待狀態(tài),直到某個(gè)線程將state屬性置為0,其就會(huì)喚醒在await()方法中等待的線程。
構(gòu)造方法
public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); } Sync(int count) { setState(count); } protected final void setState(int newState) { state = newState; }
阻塞
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); } public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); //arg為1,不為0 ,返回-1,這里小于0 if (tryAcquireShared(arg) < 0) //入隊(duì)阻塞 doAcquireSharedInterruptibly(arg); } protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; }
入隊(duì)阻塞
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException { //入隊(duì),創(chuàng)建節(jié)點(diǎn) 使用共享模式 final Node node = addWaiter(Node.SHARED); boolean failed = true; try { for (;;) { //獲取當(dāng)前節(jié)點(diǎn)的前軀節(jié)點(diǎn) final Node p = node.predecessor(); //如果節(jié)點(diǎn)為head節(jié)點(diǎn) if (p == head) { //阻塞動(dòng)作比較重,通常會(huì)再嘗試獲取資源,沒有獲取到返回負(fù)數(shù) int r = tryAcquireShared(arg); if (r >= 0) { setHeadAndPropagate(node, r); p.next = null; // help GC failed = false; return; } } //判斷是否可以阻塞 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }
countDown方法減一
public void countDown() { sync.releaseShared(1); } 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)) //減到0的時(shí)候返回true,進(jìn)行喚醒 return nextc == 0; } }
喚醒邏輯
private void doReleaseShared() { for (;;) { Node h = head; if (h != null && h != tail) { int ws = h.waitStatus; if (ws == Node.SIGNAL) { //wa為-1時(shí),將其狀態(tài)設(shè)置為0,并且喚醒 if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; unparkSuccessor(h); } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // loop on failed CAS } if (h == head) // loop if head changed break; } } private void unparkSuccessor(Node node) { int ws = node.waitStatus; if (ws < 0) //ws狀態(tài)小于0就將其設(shè)置為0 compareAndSetWaitStatus(node, ws, 0); Node s = node.next; if (s == null || s.waitStatus > 0) { s = null; for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null) //s不為空就調(diào)用unpark LockSupport.unpark(s.thread); }
五. CountDownLatch與Thread.join的區(qū)別
- CountDownLatch的作用就是允許一個(gè)或多個(gè)線程等待其他線程完成操作,看起來有點(diǎn)類似join() 方法,但其提供了比 join() 更加靈活的API。
- CountDownLatch可以手動(dòng)控制在n個(gè)線程里調(diào)用n次countDown()方法使計(jì)數(shù)器進(jìn)行減一操作,也可以在一個(gè)線程里調(diào)用n次執(zhí)行減一操作。
- 而 join() 的實(shí)現(xiàn)原理是不停檢查join線程是否存活,如果 join 線程存活則讓當(dāng)前線程永遠(yuǎn)等待。所以兩者之間相對(duì)來說還是CountDownLatch使用起來較為靈活。
到此這篇關(guān)于Java AQS中閉鎖CountDownLatch的使用的文章就介紹到這了,更多相關(guān)Java CountDownLatch內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringMVC @RequestMapping注解作用詳解
通過@RequestMapping注解可以定義不同的處理器映射規(guī)則,下面這篇文章主要給大家介紹了關(guān)于SpringMVC中@RequestMapping注解用法的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-01-01Java網(wǎng)絡(luò)通信基礎(chǔ)編程(必看篇)
下面小編就為大家?guī)硪黄狫ava網(wǎng)絡(luò)通信基礎(chǔ)編程(必看篇)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-05-05Spring Boot整合Spring Security簡(jiǎn)單實(shí)現(xiàn)登入登出從零搭建教程
這篇文章主要給大家介紹了關(guān)于Spring Boot整合Spring Security簡(jiǎn)單實(shí)現(xiàn)登入登出從零搭建的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看吧2018-09-09Spring中@DependsOn注解的作用及實(shí)現(xiàn)原理解析
這篇文章主要介紹了Spring中@DependsOn注解的作用及實(shí)現(xiàn)原理解析,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03Java關(guān)鍵字詳解之final static this super的用法
this用來調(diào)用目前類自身的成員變量,super多用來調(diào)用父類的成員,final多用來定義常量用的,static定義靜態(tài)變量方法用的,靜態(tài)變量方法只能被類本身調(diào)用,下文將詳細(xì)介紹,需要的朋友可以參考下2021-10-10Java復(fù)制(拷貝)數(shù)組的4種方法:arraycopy()方法、clone() 方法、copyOf()和copyOfRa
這篇文章主要介紹了Java復(fù)制(拷貝)數(shù)組的4種方法:arraycopy()方法、clone() 方法、copyOf()和copyOfRan,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01MyBatis-Plus結(jié)合Layui實(shí)現(xiàn)分頁(yè)方法
MyBatis-Plus 使用簡(jiǎn)單,本文主要介紹使用 service 中的 page 方法結(jié)合 Layui 前端框架實(shí)現(xiàn)分頁(yè)效果,具有一定的參考價(jià)值,感興趣的可以了解一下2021-08-08Java微信公眾平臺(tái)開發(fā)(13) 微信JSSDK中Config配置
這篇文章主要為大家詳細(xì)介紹了Java微信公眾平臺(tái)開發(fā)第十三步,微信JSSDK中Config配置,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04Java樹形結(jié)構(gòu)數(shù)據(jù)生成導(dǎo)出excel文件方法記錄
最近好像得罪了poi,遇到的都是導(dǎo)出word、Excel、pdf的問題,下面這篇文章主要給大家介紹了關(guān)于Java樹形結(jié)構(gòu)數(shù)據(jù)生成導(dǎo)出excel文件的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2021-10-10