Java8 CountDownLatch 源码分析
一、CountDownLatch 概述
1.1 什么是 CountDLatch
閉鎖(CountDownLatch)是 java.util.concurrent 包下的一種同步工具類。閉鎖可以用來確保某些活動直到其他活動都完成后才執行。
閉鎖相當于一扇門:在閉鎖到達結束狀態之前,這扇門一直是關閉的,并且沒有任何線程能通過,當達到結束狀態時,這扇門會打開,并允許所有的線程通過。
1.2 CountDownLatch 的應用場景
- 確保某個計算在其需要的所有資源都被初始化之后才執行
- 確保某個服務在其依賴的所有其他服務都已經啟動之后才啟動
- 等待直到每個操作的所有參與者都就緒再執行(比如打麻將時需要等待四個玩家就緒)
1.3 CountDownLatch 簡單應用
我們知道 4 個人玩紙牌游戲一定會先等所有玩家就緒后才會發牌,下面我們就來用閉鎖簡單的模擬一下。
public class CountDownLatchTest {/*** 初始化需要等待的 4 個事件*/private static CountDownLatch latch = new CountDownLatch(4);public static void main(String[] args) throws InterruptedException {// 創建 4 個線程分別代表 4 個玩家new Thread(() -> { System.out.println("玩家 1 已就緒"); latch.countDown(); }).start();new Thread(() -> { System.out.println("玩家 2 已就緒"); latch.countDown(); }).start();new Thread(() -> { System.out.println("玩家 3 已就緒"); latch.countDown(); }).start();new Thread(() -> { System.out.println("玩家 4 已就緒"); latch.countDown(); }).start();// 所有玩家就緒前一直阻塞latch.await();System.out.println("所有玩家已就緒,請發牌");} }下面是控制臺輸出:
二、CountDownLatch 原理分析
CountDownLatch 底層是基于 AQS 實現的,如果不懂 AQS 原理的小伙伴需要先了解下 AQS 再來看這篇文章。
2.1 API 相關方法
構造函數:
public CountDownLatch(int count) {if (count < 0) throw new IllegalArgumentException("count < 0");// 初始化 count 值this.sync = new Sync(count);}CountDownLatch 內部有一個 Sync 同步對象,這個對象是一個內部類實現了 AQS,下面我們會具體來看方法實現。
await 方法:
public void await() throws InterruptedException {// 共享式檢查是否中斷,如果中斷拋出異常// 調用 tryAcquireShared 方法嘗試獲取同步狀態,當閉鎖內的線程執行完畢后嘗試獲取成功,直接返回sync.acquireSharedInterruptibly(1);}countDown 方法:
public void countDown() {// 調用 releaseShared 每次使同步狀態值減 1sync.releaseShared(1);}通過上面的 API 我們應該能知道其大概的原理了,在 CountDownLatch 初始化的時候會有一個初始的同步狀態值,這個同步狀態值可以理解為放行前的所要執行的線程數,每次調用 countDown 方法時就把同步狀態值減 1,await 方法會自旋檢查同步狀態值是否為 0,當不為 0 時會阻塞線程,當為 0 時會直接返回,該方法是支持相應 中斷的,當線程中斷時會拋出異常。因此該方法可以理解為一扇門,只有當指定數量的線程執行完后,才會執行后續的代碼。
上面我們已經理解了大概的流程,下面來看下具體的實現代碼。
2.2 Sync 同步類
private static final class Sync extends AbstractQueuedSynchronizer {// 初始化閉鎖 count 值Sync(int count) {setState(count);}int getCount() {return getState();}// 通過共享方式嘗試獲取鎖protected int tryAcquireShared(int acquires) {return (getState() == 0) ? 1 : -1;}// 通過共享方式嘗試釋放鎖// 因為該方法是線程共享的,因此需要通過 CAS 操作保證線程安全protected boolean tryReleaseShared(int releases) {for (;;) {int c = getState();// 同步狀態值在上一次置 0 時已經放行,因此返回 falseif (c == 0)return false;// 同步狀態值 - 1int nextc = c-1;// 為 0 時返回 trueif (compareAndSetState(c, nextc))return nextc == 0;}}}內部代碼很簡單,如果你明白了 AQS 的內部原理,這些代碼是很容易理解的,如果你對這里的代碼感覺到陌生,那么你一定要好好的再去了解下 AQS 了。
AQS 的原理設計的很巧妙,相對來說也比較難理解,后面想梳理這塊內容的時候會嘗試著總結一下,如果你對這塊感興趣,也可以到我的 GitHub 去看對應的源碼。
jdk1.8 源碼閱讀:https://github.com/zchen96/jdk1.8-source-code-read
參考資料
《Java 并發編程實戰》
總結
以上是生活随笔為你收集整理的Java8 CountDownLatch 源码分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 水车柴火腊肉属于哪儿的特产
- 下一篇: 红油豆瓣酱和酱香豆瓣酱哪种更适合炒回锅肉