并發工具類—— CountDownLatch

概述

前段時間在解決請求風控服務器超時的問題時,涉及到到一個CountDownLunch的并發工具類,非常實用,順記自然就去研究了一下相關的并發工具類。
在JDK的并發包里(java.util.concurrent)提供了這樣幾個非常有用的并發工具類來解決并發編程的流程控制。分別是CountDownLatch、CyclicBarrier和Semaphore。

1. CountDownLatch

1.1 CountDownLatch是什么?

CountDownLatch打多是被用在等待多線程完成,具體來說就是允許一個或多個線程等待其他線程完成操作。

1.2 CountDownLatch原理?

API 文檔有這樣一盒解釋

A CountDownLatch is a versatile synchronization tool and can be used for a number of purposes. A CountDownLatch initialized with a count of one serves as a simple on/off latch, or gate: all threads invoking await wait at the gate until it is opened by a thread invoking countDown(). A CountDownLatch initialized to N can be used to make one thread wait until N threads have completed some action, or some action has been completed N times.

構造函數

//Constructs a CountDownLatch initialized with the given count.
public void CountDownLatch(int count) {...}

在 CountDownLunch啟動的時候。主線程必須在啟動其他線程后立即調用CountDownLatch.await()方法。這樣主線程的操作就會在這個方法上阻塞,直到其他線程完成各自的任務。

public void countDown()

在每次任務執行完直接調用,計數器就會減一操作。

public boolean await(long timeout,TimeUnit unit) throws InterruptedException
public void await() throws InterruptedException

這個方法就是用來堵塞主線程的,前者是有等待時間的,可以自定義,后者是無限等待,知道其他count 計數器為0為止。

看圖可能一下子明白

詳細的 demo 就不在這里粘貼了
如有需要傳送門

1.3 使用場景

超時機制

主線程里面設置好等待時間,如果發現在規定時間內還是沒有返回結果,那就喚醒主線程,拋棄。

開始執行前等待n個線程完成各自任務

例如應用程序啟動類要確保在處理用戶請求前,所有N個外部系統已經啟動和運行了。

死鎖檢測

一個非常方便的使用場景是,你可以使用n個線程訪問共享資源,在每次測試階段的線程數目是不同的,并嘗試產生死鎖。
若有不正之處請多多諒解,并歡迎各位大牛批評指正。

1.4 深入源碼

這里面我簡單的研究了一下CountDownLunch 源碼。
底層是由AbstractQueuedSynchronizer提供支持(后面就簡稱 AQS),所以其數據結構就是AQS的數據結構,而AQS的核心就是兩個虛擬隊列:同步隊列syncQueue 和條件隊列conditionQueue(前者數據結構是雙向鏈表,后者是單向鏈表)不同的條件會有不同的條件隊列。
本省CountDownLunch繼承的是 Object,比較簡單,但是存在內部類,Sync,繼承自AbstractQueuedSynchronizer,我簡單理解一下

private static final class Sync extends AbstractQueuedSynchronizer {
        // 版本號
        private static final long serialVersionUID = 4982264981922014374L;
        
        // 構造器
        Sync(int count) {
            setState(count);
        }
   
        // 返回當前計數
        int getCount() {
            return getState();
        }

        // 試圖在共享模式下獲取對象狀態
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

        // 試圖設置狀態來反映共享模式下的一個釋放
        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)) // 比較并且設置成功
                    return nextc == 0;
            }
        }
    }

1.4.1核心函數分析

  • await函數
    此函數將會使當前線程在鎖存器倒計數至零之前一直等待,除非線程被中斷。其源碼如下
public void await() throws InterruptedException{
      // 轉發到sync對象上 
      sync.acquireSharedInterruptibly(1);
}

源碼可知,對CountDownLatch對象的await的調用會轉發為對Sync的acquireSharedInterruptibly(從AQS繼承的方法)方法的調用。

public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

這里先檢測了線程中斷狀態,中斷了則拋出異常,接下來調用tryAcquireShared,tryAcquireShared是Syn的實現的

        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

其實就是簡單的獲取了同步器的state,判斷是否為0.
接下來是

private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    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);
        }
    }

關鍵點 我看到的是
parkAndCheckInterrupt()

  private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
 }

執行到此處時,線程會阻塞,知道有其他線程喚醒此線程,執行await之后,上文中的主線程阻塞在這。


整個調用鏈路如下
  • countDown函數
    此函數將遞減鎖存器的計數,如果計數到達零,則釋放所有等待的線程
void countDown() { 
    sync.releaseShared(1);
}

可以看出 對countDown的調用轉換為對Sync對象的releaseShared(從AQS繼承而來)方法的調用。
這里面的具體原理能力有限,有點看不懂,CAS相關的東西。

1.5 小結

不得不說countdownlatch是一個很高的線程控制工具,極大的方便了我們開發。由于知識能力有限,上面是自己的一點見識,有什么錯誤還望提出,便于我及時改進。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容