CountDownLatch原始碼分析

辣雞小籃子發表於2020-09-07

CountDownLatch原始碼分析

CountDowntLatch的作用是讓主執行緒等待所有的子執行緒執行完畢之後再進行執行,同時它是基於AQS進行實現的,所以它內部肯定是通過自定義AQS共享模式下的同步器來實現的,該同步器需要重寫AQS提供的tryAcquireShared()以及tryReleaseShared()方法,告訴AQS是否嘗試獲取同步資源以及釋放同步資源成功。

AQS子類需要定義以及維護同步狀態的值,在CountDownLatch中,同步狀態state的值為同步資源的個數。


CountDownLatch的結構

public class CountDownLatch {
    
    /**
     * 存在一個AQS共享模式下的同步器
     */
    private static final class Sync extends AbstractQueuedSynchronizer {
       // ......
    }

    // 存在一個全域性的同步器屬性
    private final Sync sync;
    
    /**
     * 構建方法初始化同步器,並指定同步資源的個數
     */
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
    
    /**
     * 讓主執行緒進行阻塞
     */
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }
    
    /**
     * 讓倒數器-1
     */
    public void countDown() {
        sync.releaseShared(1);
    }

    public long getCount() {
        return sync.getCount();
    }

    public String toString() {
        return super.toString() + "[Count = " + sync.getCount() + "]";
    }
}

可以看到CountDownLatch中定義了一個同步器,然後存在一個全域性的同步器屬性,然後通過構建方法來初始化同步器,並且指定同步器中同步資源的個數。

CountDownLatch的await()方法將會呼叫同步器的acquireSharedInterruptibly()方法,countDown()方法將會呼叫同步器的releaseShared()方法。


剖析同步器

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) {
        // 只有當同步狀態的值為0,方法才返回true
        return (getState() == 0) ? 1 : -1;
    }

    /**
     * 嘗試釋放同步資源
     */
    protected boolean tryReleaseShared(int releases) {
        // 死迴圈保證CAS操作成功
        for (;;) {
            int c = getState();
            if (c == 0)
                return false;
            // 讓同步狀態的值-1
            int nextc = c-1;
            // 只有當執行緒釋放同步資源後,同步狀態的值0時,該方法才會返回true
            if (compareAndSetState(c, nextc))
                return nextc == 0;
        }
    }
}

tryAcquireShared()方法用於嘗試獲取同步資源,正常情況下,如果執行緒獲取失敗則返回false,否則返回剩餘的可用資源個數(state - 要獲取的資源個數),但是在CountDownLatch同步器的tryAcquireShared()中,只有當等待狀態的值為0時,方法才返回true,否則返回false。

tryReleaseShared()方法用於嘗試釋放同步資源,正常情況下,將同步狀態的值累加,也就是恢復,然後返回true,但是在CountDownLatch同步器的tryReleaseShared()方法中,並沒有累加同步狀態的值,而是當執行緒每次釋放後,將同步狀態的值-1,只有當執行緒釋放同步狀態後,state的值為0,該方法才會返回true。


流程總結

1.首先建立一個CountDownLatch例項,並指定倒數器的閾值。

2.主執行緒呼叫CountDownLatch的await()方法進行阻塞,該方法呼叫同步器的acquireSharedInterruptibly()方法,該方法內部會呼叫tryAcquireShared()方法,嘗試獲取同步資源,但是在tryAcquireShared()方法中,只有當同步狀態的值為0時,方法才會返回true,由於目前同步狀態的值不為0,因此方法返回false,因此該執行緒將會封裝成Node節點,然後加入到等待佇列當中,該執行緒將會進行阻塞。

3.子執行緒呼叫CountDownLatch的countDown()方法讓倒數器-1,該方法呼叫同步器releaseShared()方法,該方法內部將會呼叫tryReleaseShared()方法,嘗試釋放同步資源,但是在tryReleaseShared()方法中,會將同步狀態的值-1,同時只有當執行緒釋放同步資源後,同步狀態的值為0時,該方法才會返回true,否則返回false,如果tryReleaseShared()方法返回false,那麼就不做任何處理,只有當該方法返回true,也就是所有的子執行緒都執行了countDown()方法,將同步狀態的值設定為0,當該方法返回true時,那麼就會喚醒離頭節點最近的同時等待狀態不為CANCELLED的後繼節點,也就是主執行緒,然後主執行緒呼叫tryAcquireShared()方法嘗試獲取同步資源,由於當前同步狀態的值已經為0,因此tryAcquireShared()方法返回true,然後主執行緒直接返回,做自己的事情。


FAQ

CountDownLatch為什麼不能重用?

不能重用,因此當主執行緒被喚醒後,然後呼叫tryAcquireShared()方法獲取了同步資源,然後就直接返回,做自己的事情,永遠都不會釋放同步資源,因此不能重用。

相關文章