【JDK源碼分析】深入源碼分析CountDownLatch

来源:https://www.cnblogs.com/d-homme/archive/2018/07/26/9375105.html
-Advertisement-
Play Games

前言 CountDownLatch是一個閉鎖實現,它可以使一個或者多個線程等待一組事件發生。它包含一個計數器,用來表示需要等待的事件數量,coutDown方法用於表示一個事件發生,計數器隨之遞減,而await方法等待計數器為0之前一直阻塞。它是基於AQS的共用鎖來實現的,其中使用了較多的AQS的方法 ...


前言

CountDownLatch是一個閉鎖實現,它可以使一個或者多個線程等待一組事件發生。它包含一個計數器,用來表示需要等待的事件數量,coutDown方法用於表示一個事件發生,計數器隨之遞減,而await方法等待計數器為0之前一直阻塞。它是基於AQS的共用鎖來實現的,其中使用了較多的AQS的方法,所以在這之前最好閱讀過AQS的源碼,不嫌棄也可以查看本人之前AQS的源碼分析,有些AQS方法沒有在之前分析過的這裡涉及到了會進行分析。

源碼

我們先看它的屬性和構造器,

    // Sync為其內部類
    private final Sync sync;

    // 唯一的一個構造器
    // 構造參數count就是需要等待事件的數量
    public CountDownLatch(int count) {
        // 為了保證count >= 0
        if (count < 0) throw new IllegalArgumentException("count < 0");
        // 構造sync
        this.sync = new Sync(count);
    }

現在來看內部類Sync,它繼承了AQS,實現了共用鎖方法,下麵來看其源碼,代碼行數不多很好理解

    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

        Sync(int count) {
            // setState 為AQS更改其state變數的方法
            // 將AQS state變數設置成count
            setState(count);
        }

        int getCount() {
            // AQS的獲取state鎖狀態值
            return getState();
        }
        // 嘗試獲取共用鎖
        protected int tryAcquireShared(int acquires) {
            // 返回1表示此時鎖狀態值為0表示鎖已釋放
            // -1表示此時鎖狀態值大於0,表示出於鎖定狀態
            return (getState() == 0) ? 1 : -1;
        }
        // 嘗試釋放共用鎖(計數器遞減releases次)
        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            // 等待鎖狀態值為0或者更改鎖狀態值成功
            for (;;) {
                // 將state賦值給變數c
                int c = getState();
                if (c == 0)
                    // 此時鎖已清除
                    return false;
                // 遞減
                int nextc = c-1;
                // 比較state的狀態值是否等於C,等於將state狀態值改為nextc
                if (compareAndSetState(c, nextc))
                    // 更改成功後,如果nextc為0則返回true
                    return nextc == 0;
            }
        }
    }

await方法

await方法就是當state狀態值不為0時將當前線程阻塞,然後等待喚醒

    public void await() throws InterruptedException {
        //調用的AQS獲取共用鎖可中斷方法
        sync.acquireSharedInterruptibly(1);
    }

 

我們來看看AQS的acquireSharedInterruptibly方法

    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        // 此方法調用的是CountDownLatch內部類Sync的方法
        // 如果鎖狀態不為0,則執行doAcquireSharedInterruptibly方法
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

doAcquireSharedInterruptibly方法也是由AQS實現的

    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        // 添加一個共用鎖節點到隊列
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            // 直到線程被喚醒或者線程被中斷時跳出迴圈
            for (;;) {
                // node節點的前驅節點
                final Node p = node.predecessor();
                if (p == head) {
                    // 調用CountDownLatch內部類Sync的方法
                    // 如果鎖狀態值為0,則返回值大於0
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        // 當鎖狀態值為0,開始將note節點設置為頭節點並喚醒後繼節點
                        // 也就是隊列不斷的出列,然後喚醒後繼節點,後繼節點被喚醒後由於前驅節點被設置成頭節點,又會調用該方法進行後繼節點的喚醒
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }

                /*
                 shouldParkAfterFailedAcquire用於清除已中斷/或者取消的線程以及判斷此次迴圈是否需要掛起線程
                 parkAndCheckInterrupt 掛機當前線程
                 shouldParkAfterFailedAcquire 和 parkAndCheckInterrupt 在AQS之前博文里分析過這裡就不再分析了
                 */
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                // 表示當前線程中斷,取消獲取鎖
                // 之前分析過,略過源碼分析
                cancelAcquire(node);
        }
    }

 

setHeadAndPropagate方法,主要作用是喚醒後繼節點線程

    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; 
        // 當前節點設置為頭節點,節點關聯的線程設置為空
        setHead(node)
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                // 節點等待狀態為signal時,喚醒後繼節點線程
                doReleaseShared();
        }
    }

doReleaseShared很巧妙,噹噹前節點等待狀態為signal時,喚醒後繼節點線程

    private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                // 當前線程等待狀態為signal時表示後繼節點需要喚醒
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        // 表示h節點的狀態替換失敗,會再次迴圈判斷h節點的狀態
                        continue;            // loop to recheck cases
                    // 喚醒後繼節點
                    unparkSuccessor(h);
                }
                // 狀態為0時,將其改成PROPAGATE,更改失敗會再次迴圈判斷h節點的狀態
          // 這種情況發生在一個線程調用await方法,節點的等待狀態還是初始值0未來得及被修改,剛好state被置為0然後調用了doReleaseShared方法
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

countDown方法

countDown方法遞減state值,當值為0時,依次喚醒等待的線程

    public void countDown() {
        // 遞減一次state值,知道state為0時喚醒等待中的線程
        sync.releaseShared(1);
    }
public final boolean releaseShared(int arg) { // 嘗試將state減去arg if (tryReleaseShared(arg)) { // state為0時喚醒線程 doReleaseShared(); return true; } return false; }

到此分析完畢。

總結

  1. 通過源碼知道CountDownLatch 不能像CyclicBarrier那樣使用完畢後還可以復用;
  2. CountDownLatch 是通過共用鎖來實現的,它的構造參數就是AQS state的值;
  3. 由於內部類繼承了AQS,所以它內部也是FIFO隊列,同時也一樣是前驅節點喚醒後繼節點。

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.工廠模式 工廠模式是我們最常用的實例化對象模式了,是用工廠方法代替new操作的一種模式。著名的Jive論壇 ,就大量使用了工廠模式,工廠模式在Java程式系統可以說是隨處可見。因為工廠模式就相當於創建實例對象的new,我們經常要根據類Class生成實例對象,如A a=new A() 工廠模式也是 ...
  • 前言 之前學多線程的時候沒有學習線程的同步工具類(輔助類)。ps:當時覺得暫時用不上,認為是挺高深的知識點就沒去管了.. 在前幾天,朋友發了一篇比較好的Semaphore文章過來,然後在瀏覽博客的時候又發現面試還會考,那還是挺重要的知識點。於是花了點時間去瞭解一下。 Java為我們提供了 三個同步工 ...
  • #include #include //提供malloc()原型 /* 線性表需要的方法: 1、 List MakeEmpty():初始化一個空線性表 2、 EementType FindKey(int K, List L):根據位序K,返回相應元素 3、 int Find(ElementType ... ...
  • 前言 這次分析信號量Semaphore,為什麼稱之為信號量呢?是因為它可以控制同時訪問某個資源的操作數量或是同時執行某個指定操作的數量。就好比它像一個租賃汽車的公司,租賃公司的汽車的數量是固定的,用完需要歸還,用之前需要去租借(acquire 前提是還有可用的汽車),如果汽車都被租出去了,那隻能等到 ...
  • 首先感謝授課XXX老師。 1.什麼是線程安全問題 當多個線程共用同一個全局變數,做寫的操作時候,可能會受到其他線程的干擾,導致數據出現問題,這種現象就叫做線程安全問題。做讀的時候不會產生線程安全問題。 什麼安全:多個線程同時共用一個全局變數,做寫操作的時候會發生線程安全。 多個線程共用同一個局部變數 ...
  • 最近在做一個項目的時候,需要使用golang來調用操作系統中的命令行,來執行shell命令或者直接調用第三方程式,這其中自然就用到了golang自帶的exec.Command. 但是如果直接使用原生exec.Command會造成大量的重覆代碼,網上搜了一圈又沒有找到對exec.Command相應的封 ...
  • 本文為CUBA-Platform簡介 ,一個結合了可靠架構、企業級應用程式“必備”功能和應用程式快速開發工具的開源框架。 ...
  • <! done   簡單的‘Hello World!’   Python命令行 假設你已經安裝好了Python, 那麼在Linux命令行輸入: $python 將直接進入python。然後在命令行提示符>>>後面輸入: >>>print('He ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...