CountDownLatch源碼閱讀筆記

CountDownLatch源碼閱讀

await方法如何實現線程等待

await方法,由CountDownLatch.Sync.acquireSharedInterruptibly代理完成,實際上由Sync的父類AbstractQueuedSynchronizer實現了該方法

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

由於arg參數入參固定爲1, 而Sync實現的tryAcquireShared方法對於入參數1返回-1,因此實際相當於調用瞭如下代碼,Thread.interrupted確保了當前線程不是interrupted的狀態

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

AbstractQueuedSynchronizer類的doAcquireSharedInterruptibly方法,其中addWaiter方法是往Node鏈表最後增加一個Node節點並返回該節點,Node對象中存儲了當前線程t

    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);
        }
    }

實際上由於arg值固定爲1,上面方法相當於如下

    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

shouldParkAfterFailedAcquire方法判斷當前新增的節點(node)的前置節點(p)是否持有信號,並在沒有持有信號的情況下,使其變更爲持有信號狀態(使state值設爲SIGNAL)。doAcquireSharedInterruptibly中for循環直到shouldParkAfterFailedAcquire方法判斷node的前置節點持有信號時,纔會調用parkAndCheckInterrupt方法。

parkAndCheckInterrupt代碼便是阻塞代碼真正所在的位置,該功方法代碼很簡單,直接委託LockSupport.park(this)完成,LockSupport.park(this)代碼如下

    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }

setBlocker時native的方法,它的作用是將第二個參數存儲在第一個參數(線程)的堆內存中(即這是一個直接操作堆內存存儲的實現),上面park方法中UNSAFE.park爲阻塞功能的實現,點進去看發現也是一個native修飾的方法,即真正阻塞功能的還是由底層實現的,沒法看到具體的代碼。

countDown方法如何實現計數遞減並取消阻塞

await方法一樣,countDown方法也是將工作委託給了Sync類的方法完成。CountDownLatch.Sync.releaseShared代理完成該功能,CountDownLatch.Sync.releaseShared實際上也是已經由Sync的父類AbstractQueuedSynchronizer實現了,該方法如下

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

tryReleaseShared方法嘗試進行計數(即state)遞減,tryReleaseShared方法中與前面一些方法一樣,使用了原子性的方法compareAndSetState完成遞減操作,該原子性操作保證了不會出現兩次countDown之後state只遞減一次的情況。當本次遞減後計數達到0返回true,原先計數已經爲0或者本次遞減後計數不爲0,返回false。tryReleaseShared返回true後,才能調用doReleaseShared完成Node恢復阻塞的線程。

    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;
        }
    }

如下所示爲doReleaseShared方法代碼,當ws == Node.SIGNAL(持有信號)時,調用compareAndSetWaitStatus嘗試使Node不再持有信號(state值從SIGNAL變爲0,compareAndSetWaitStatus調用的unsafe.compareAndSwapInt方法是原子性的方法),釋放信號成功則進行unparkSuccessor的調用,不成功則繼續循環(一般另外一條線程同時操作這個node的時候纔會導致不成功),當ws == 0時,將該Node標識轉爲PROPAGATE後不再作處理,這種情況基本與CountDownLatch的業務無關,只是AbstractQueuedSynchronizer對Node鏈表結構的維護工作

    private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

最後unparkSuccessor方法,找到head Node的下一個Node s,找到阻塞的線程s.thread, 調用LockSupport.unpark(s.thread)解除線程阻塞(核心代碼UNSAFE.unpark(thread)方法,是native方法,發現一點有意思的情況:該方法運行完會改變head的指向,雖然與這裏研究的關係不大,但傳入參數是thread卻能改變Sync的head Node指針確是挺有意思的)

private void unparkSuccessor(Node node) {
        
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

總結

CountDownLatch基本由CountDownLatch.Sync代理,Sync大量調用了sun.misc.Unsafe的代碼。使用Unsafe的用於改變變量值的原子性方法,減少一些鎖的使用;另外一點是使用park和sun.misc.Unsaft.parksun.misc.Unsaft.unpark方法來實現指定線程的阻塞與解除阻塞。可惜的是sun.misc.Unsafe類oracle並不開放給外部代碼使用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章