「原創」Java並發編程系列22|倒計時器CountDownLatch

2020-12-27 酷扯兒

本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫

並發編程中常遇到這種情況,一個線程需要等待另外多個線程執行後再執行。遇到這種情況你一般怎麼做呢?今天就介紹一種JDk提供的解決方案來優雅的解決這一問題,那就是倒計時器CountDownLatch。本文將分以下兩部分介紹:

CountDownLatch的使用CountDownLatch源碼分析1. CountDownLatch的使用

CountDownLatch的作用是讓線程等待其它線程完成一組操作後才能執行,否則就一直等待。

舉個開會的例子:

老闆先進入會議室準備材料等待5個員工陸續進入會議室員工到齊開始開會老闆不能一來就開會,必須要等員工都到了再開會,用CountDownLatch實現如下:

public class CountDownLatchTest {private static CountDownLatch countDownLatch = new CountDownLatch(5); // Boss線程,等待員工到齊開會 static class BossThread extends Thread { @Override public void run() { System.out.println("Boss進入會議室準備材料..."); System.out.println("Boss在會議室等待..."); try { countDownLatch.await(); // Boss等待 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Boss等到人齊了,開始開會..."); } } // 員工到達會議室 static class EmpleoyeeThread extends Thread { @Override public void run() { System.out.println("員工" + Thread.currentThread().getName() + ",到達會議室...."); countDownLatch.countDown(); } } public static void main(String[] args) { // Boss線程啟動 new BossThread().start(); // 員工到達會議室 for (int i = 0; i < countDownLatch.getCount(); i++) { new EmpleoyeeThread().start(); } }}

控制臺輸出:

Boss進入會議室準備材料...Boss在會議室等待...員工Thread-2,到達會議室....員工Thread-3,到達會議室....員工Thread-4,到達會議室....員工Thread-1,到達會議室....員工Thread-5,到達會議室....Boss等到人齊了,開始開會...

總結CountDownLatch的使用步驟:(比如線程A需要等待線程B和線程C執行後再執行)

創建CountDownLatch對象,設置要等待的線程數N(這裡是2);等待線程A調用await()掛起;線程B執行後調用countDown(),使N-1;線程C執行後調用countDown(),使N-1;調用countDown()後檢查N=0了,喚醒線程A,在await()掛起的位置繼續執行。2. CountDownLatch源碼分析

CountDownLatch是通過一個計數器來實現的,當我們在new 一個CountDownLatch對象的時候需要帶入該計數器值,該值就表示了線程的數量。每當一個線程完成自己的任務後,計數器的值就會減1。當計數器的值變為0時,就表示所有的線程均已經完成了任務,然後就可以恢復等待的線程繼續執行了。

2.1 類結構

CountDownLatch只有一個屬性Sync,Sync是繼承了AQS的內部類。

創建CountDownLatch時傳入一個count值,count值被賦值給

AQS.state

CountDownLatch是通過AQS共享鎖實現的,AQS這篇文章中詳細講解了AQS獨佔鎖的原理,AQS共享鎖和獨佔鎖原理只有很細微的區別,這裡大致介紹下:

線程調用acquireSharedInterruptibly()方法獲取不到鎖時,線程被構造成結點進入AQS阻塞隊列。當有線程調用releaseShared()方法將當前線程持有的鎖徹底釋放後,會喚醒AQS阻塞隊列中等鎖的線程,如果AQS阻塞隊列中有連續N個等待共享鎖的線程,就將這N個線程依次喚醒

public class CountDownLatch {private static final class Sync extends AbstractQueuedSynchronizer { Sync(int count) { setState(count); } } private final Sync sync; public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); }}

2.2 await()

await()

是將當前線程阻塞,理解

await()

的原理就是要弄清楚await()是如何將線程阻塞的。

await()

調用的就是AQS獲取共享鎖的方法。當

AQS.state=0

時才能獲取到鎖,由於創建CountDownLatch時設置了

state=count

,此時是獲取不到鎖的,所以調用

await()

的線程掛起並構造成結點進入AQS阻塞隊列。

創建CountDownLatch時設置AQS.state=count,可以理解成鎖被重入了count次。await()方法獲取鎖時鎖被佔用了,只能阻塞。

/*** CountDownLatch.await()調用的就是AQS獲取共享鎖的方法acquireSharedInterruptibly() */public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1);}/** * 獲取共享鎖 * 如果獲取鎖失敗,就將當前線程掛起,並將當前線程構造成結點加入阻塞隊列 * 判斷是否獲取鎖成功的方法由CountDownLatch的內部類Sync實現 */public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0) // 嘗試獲取鎖的方法由CountDownLatch的內部類Sync實現 doAcquireSharedInterruptibly(arg); // 獲取鎖失敗,就將當前線程掛起,並將當前線程構造成結點加入阻塞隊列}/** * CountDownLatch.Sync實現AQS獲取鎖的方法 * 只有AQS.state=0時獲取鎖成功。 * 創建CountDownLatch時設置了state=count,調用await()時state不為0,返回-1,表示獲取鎖失敗。 */protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1;}

2.3 countDown()

countDown()

方法是將count-1,如果發現count=0了,就喚醒阻塞的線程。

countDown()

調用AQS釋放鎖的方法,每次將state減1。當state減到0時是無鎖狀態了,就依次喚醒AQS隊列中阻塞的線程來獲取鎖,繼續執行邏輯代碼。

/*** CountDownLatch.await()調用的就是AQS釋放共享鎖的方法releaseShared() */public void countDown() { sync.releaseShared(1);}/** * 釋放鎖 * 如果鎖被全部釋放了,依次喚醒AQS隊列中等待共享鎖的線程 * 鎖全部釋放指的是同一個線程重入了N次需要N次解鎖,最終將state變回0 * 具體釋放鎖的方法由CountDownLatch的內部類Sync實現 */public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { // 釋放鎖,由CountDownLatch的內部類Sync實現 doReleaseShared(); // 鎖全部釋放之後,依次喚醒等待共享鎖的線程 return true; } return false;}/** * CountDownLatch.Sync實現AQS釋放鎖的方法 * 釋放一次,將state減1 * 如果釋放之後state=0,表示當前是無鎖狀態了,返回true */protected boolean tryReleaseShared(int releases) { for (;;) { int c = getState(); if (c == 0) return false; // state每次減1 int nextc = c-1; if (compareAndSetState(c, nextc)) return nextc == 0;// state=0時,無鎖狀態,返回true }}

總結

CountDownLatch用於一個線程A需要等待另外多個線程(B、C)執行後再執行的情況。

創建CountDownLatch時設置一個計數器count,表示要等待的線程數量。線程A調用

await()

方法後將被阻塞,線程B和線程C調用

countDown()

之後計數器count減1。當計數器的值變為0時,就表示所有的線程均已經完成了任務,然後就可以恢復等待的線程A繼續執行了。

CountDownLatch是由AQS實現的,創建CountDownLatch時設置計數器count其實就是設置

AQS.state=count

,也就是重入次數。

await()

方法調用獲取鎖的方法,由於

AQS.state=count

表示鎖被佔用且重入次數為count,所以獲取不到鎖線程被阻塞並進入AQS隊列。

countDown()

方法調用釋放鎖的方法,每釋放一次AQS.state減1,當AQS.state變為0時表示處於無鎖狀態了,就依次喚醒AQS隊列中阻塞的線程來獲取鎖,繼續執行邏輯代碼。

相關焦點

  • 「原創」Java並發編程系列06|你不知道的final
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫final在Java中是一個保留的關鍵字,可以修飾變量、方法和類。那麼fianl在並發編程中有什麼作用呢?本文就在對final常見應用總結基礎上,講解final並發編程中的應用。
  • Java並發工具三劍客之CountDownLatch源碼解析
    CountDownLatch是Java並發包下的一個工具類,latch是門閂的意思,顧名思義,CountDownLatch就是有一個門閂擋住了裡面的人(線程)出來,當count減到0的時候,門閂就打開了,人(線程)就可以出來了。
  • Java中的並發工具類CountDownLatch
    Java中的並發工具類在多線程編程的時候,有時候需要控制並發流,Java本身提供了幾個控制並發的工具類,比如CountDownLatch,CyclicBarrier,SemaphoreSemaphore用來控制同時訪問資源的線程數量,比如用來限制流量,限制並發數等。acquire()方法是獲取一個通行證,releas()方法是歸還通行證。比如進小區的安檢,只能一個一個的來,代碼實現如下。
  • 「原創」Java並發編程系列33|深入理解線程池(上)
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫並發編程必不可少的線程池,接下來分兩篇文章介紹線程池,本文是第一篇。介紹1.1 使用場景並發編程可以高效利用CPU資源,提升任務執行效率,但是多線程及線程間的切換也伴隨著資源的消耗。當遇到單個任務處理時間比較短,但需要處理的任務數量很大時,線程會頻繁的創建銷毀,大量的時間和資源都會浪費在線程的創建和銷毀上,效率很低。
  • Java項目實踐,CountDownLatch實現多線程閉鎖
    摘要本文主要介紹Java多線程並發中閉鎖(Latch)的基本概念、原理、示例代碼、應用場景,通過學習,可以掌握多線程並發時閉鎖(Latch)的使用方法。概念「閉鎖」就是指一個被鎖住了的門將線程a擋在了門外(等待執行),只有當門打開後(其他線程執行完畢),門上的鎖才會被打開,a才能夠繼續執行。
  • 「原創」Java並發編程系列36|FutureTask
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫線程池源碼中出現了很多Callable、Future、FutureTask等以前沒介紹過的接口,尤其是線程池提交任務時總是把任務封裝成FutureTask,今天就來為大家解惑:
  • 一文搞懂 CountDownLatch 用法和源碼!
    = new CountDownLatch(5);         Increment increment = new Increment(latch);         Decrement decrement = new Decrement(latch);
  • Java編發編程中CountDownLatch到底是個啥?
  • 「原創」為什麼java中一切都是對象,還要有static?
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫目錄1、static的「由來」2、static的使用場景3、關於static的常見問題4、總結Java是一種面向對象編程的語言,而對象是客觀存在的事物,對同類對象抽象出其共性,便是Java中的類,類是對象的模子,具有相同屬性和方法的一組對象的集合。
  • 探索Java中的網絡編程技術
    承蒙關照~探索Java中的網絡編程技術網絡編程就是io技術和網絡技術的結合,網絡模型的定義,只要共用網絡模型就可以兩者連接.網絡模型參考.一座塔有七層,我們需要闖關.第一層物理層->第二層數據鏈路層->第三層網絡層->第四層傳輸層->第五層會話層->第六層表示層->第七層應用層.
  • 萬字概覽 Java 虛擬機
    Stack 為什麼是線程私有的,這涉及到「棧上分配」和「TLAB」兩個概念。棧上分配所謂棧上分配就是允許將對象直接分配在棧上,而不用分配到 Heap 中。這樣對象會隨著棧幀的出棧自動銷毀,不用等待 GC 進行回收,從而提高性能。但是要實現棧上分配是非常複雜的,涉及到「逃逸分析」和「標量替換」兩項技術。
  • Java並發包CountDownLatch、CyclicBarrier、Semaphore原理解析
    組隊準備,房間已滿不可加入線程Thread-10組隊準備,房間已滿不可加入線程Thread-11組隊準備,房間已滿不可加入線程Thread-12組隊準備,房間已滿不可加入線程Thread-13組隊準備,房間已滿不可加入本案例中有兩個線程都調用了latch.await
  • Java 9正式發布,新特性解讀
    另外 JVMCI (JEP 243: Java-Level JVM Compiler Interface)等特性,對於整個程式語言的發展,可能都具有非常重要的意義,雖然未必引起了廣泛關注。這話說的有點籠統,我談一些自己的體會,Java 代碼雖然進行了一些類型推斷等改進,更易用的集合 API 等,但仍然給開發者留下了過於刻板、形式主義的印象,這是一個長期的改進方向,例如,JEP 286: Local-Variable Type Inference;持續改進並發計算框架,Java 的並發特性非常強大和系統,但某種程度上過於複雜,在今年的 JVMLS 上,阿里巴巴 AJDK 組介紹了利用協程改進並發的實踐
  • 多任務並發編程需要學習的內容有哪些?
    Python多任務並發編程需要學習的內容有哪些?並發編程的目的是為了讓程序運行得更快,分工,高效地拆解任務並分配給線程;同步,線程之間如何協作; 互斥,保證同一時刻只允許一個線程訪問共享資源。需要學習多線程、多進程的創建,互斥鎖,死鎖,集全局變量等問題的解決方案。
  • 這些倒計時器修好了
    長江日報訊(記者夏晶)25日,長江日報APP刊登了《交通信號燈何時能全都亮起來》,報導了本報交通建議徵集群中,市民反映的關於部分路口交通信號燈倒計時器不亮的問題,武漢市公安局交通管理局對此積極回應。
  • Latch-up
    晶片中的latch up和ESD是一個很大的方向,有些大公司會有專門的設計團隊。本期講latch up為什麼要提及ESD呢?原因很簡單,這個latch up跟ESD相關。       相信很多IC designer對latch up都不陌生,它是指晶片內部觸發寄生npn和pnp引起電源地之間出現低阻通路進而導致電源地之間出現大電流的一種現象。
  • Java學到什麼程度才能叫精通?
    熟練掌握Java編程、熟悉Java高並發開發手段、對JVM 虛擬機(Java內存區域、虛擬機垃圾算法、虛擬垃圾收集器、JVM內存管理)有一定研究。熟練掌握SOA分布式系統開發,具有優化系統性能、提高系統並發量以及系統可用性的實際經驗。
  • 工控上位機編程學習技巧
    java程式語言(第三版)---java四大名著----James Gosling(java之父)java編程思想(第2版)----java四大名著----Bruce Eckeljava編程思想(第3版)----java四大名著-Bruce Eckeljava 2核心技術 卷I:基礎知識(原書第7版)---java四大名著Cay Horstmann
  • 編程貓的「未來創作者」:從8歲編程發明家到12歲AI開發者
    也難怪《機智過人》主持人撒貝寧感慨到,「如果我們不學習,率先取代我們的不是人工智慧,而是我們的下一代。」而作為行業領跑者,編程貓以「培養未來創作者」為初心理念,基於9級梯度式的完善課程體系,確保了從0基礎新手學員到圖像化編程專業級精英群體的全面覆蓋,學完整套課程的學員可達到史丹福大學計算機系畢業生水準。更重要的是,編程貓強調寓教於樂,以PBL項目驅動學習方式、遊戲化學習方法和多維度的考核方式有效激勵青少年學習編程。
  • 知乎神回復:如果一定要在C+和Java中選擇,應該選擇哪一種?
    結論: (1)如果你是 計算機科班,大一學生,不需要立即找工作,想提高自己,那我的建議是: 把手上有關java 但是學習C++ 同樣會給你帶來很多收益(前提是學好的情況下): (1)你會變得自信,在有C++ 基礎上,學習go 1天,java的學習也就