【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
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...