CountDownLatch 概述和原始碼分析

許佳佳233發表於2018-09-01

概述

原始碼中對這個類的描述如下:

A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.

意思大概是:

它是一個執行緒同步的助手,能夠讓一個或者多個執行緒在一組操作完成之前等待。

簡單場景(例子)

現在有10個人開會,在10人人全部到達會議室之前,“開會”這個執行緒就要一直等待。

程式碼:

import java.util.concurrent.CountDownLatch;

public class CountDownLatchTest {

    public static void main(String[] args) throws InterruptedException {

        final CountDownLatch countDownLatch = new CountDownLatch(10);

        for (int i = 0; i < 10; i++) {
            final int Number = i + 1;

            new Thread(new Runnable() {
                public void run() {
                    try {
                        Thread.sleep((long) (Math.random() * 3000));
                        System.out.println("No." + Number + " arrived");
                    } catch (InterruptedException e) {
                    } finally {
                        countDownLatch.countDown();
                        System.out.println("CountDownLatch.count:" +countDownLatch.getCount());
                    }
                }
            }).start();
        }

        System.out.println("Wait");
        countDownLatch.await();
        System.out.println("Start");
    }
}

執行結果:

Wait
No.7 arrived
CountDownLatch.count:9
No.10 arrived
CountDownLatch.count:8
No.4 arrived
CountDownLatch.count:7
No.5 arrived
CountDownLatch.count:6
No.6 arrived
CountDownLatch.count:5
No.8 arrived
CountDownLatch.count:4
No.1 arrived
CountDownLatch.count:3
No.9 arrived
CountDownLatch.count:2
No.2 arrived
CountDownLatch.count:1
No.3 arrived
CountDownLatch.count:0
Start

原始碼分析

單單CountDownLatch這個類的原始碼其實非常少,原始碼如下:

public class CountDownLatch {
    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;
            }
        }
    }

    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));
    }
    public void countDown() {
        sync.releaseShared(1);
    }
    public long getCount() {
        return sync.getCount();
    }
    public String toString() {
        return super.toString() + "[Count = " + sync.getCount() + "]";
    }
}

首先我們可以看到AbstractQueuedSynchronizer這個比較顯眼的類,它的原始碼有2000多行,比較多,要全部看完比較耗時間。
因此,我們主要還是從我們經常使用的幾個方法看起:
1、建構函式
2、await()方法
3、countDown()方法

建構函式

由原始碼最終可知,建構函式最終呼叫了AbstractQueuedSynchronizer類的setState(count)方法:

    protected final void setState(int newState) {
        state = newState;
    }
    /**
     * The synchronization state.
     */
    private volatile int state;

從AbstractQueuedSynchronizer原始碼中我們可以看到state就是一個同步狀態。

await()

由CountDownLatch原始碼可知,await()呼叫了AbstractQueuedSynchronizer的tryAcquireSharedNanos方法:

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

這裡可以看到如果中斷就會拋異常,最終如果執行成功會執行到doAcquireSharedInterruptibly這個方法:

/**
     * Acquires in shared interruptible mode.
     * @param arg the acquire argument
     */
    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.SHARED);
        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
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            throw t;
        }
    }

Node在這裡我們看做是一個等待佇列的連結串列就可以了,在等待結束後會從等待佇列中依次取物件出來執行。比如上面的例子中,等待事件就是“開會”。
這邊的parkAndCheckInterrupt()方法就是阻塞執行緒的方法,原始碼如下:

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

countDown()

由CountDownLatch原始碼可知,await()呼叫了AbstractQueuedSynchronizer的releaseShared方法:

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

這邊主要是tryReleaseShared和doReleaseShared兩個方法:

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

tryReleaseShared方法很簡單,使用CAS更改state的值。

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

doReleaseShared的作用主要是來喚醒阻塞執行緒。

doReleaseShared裡是一個死迴圈,只有Node連結串列中只有一個head時才會跳出迴圈。
unparkSuccessor就是喚醒被阻塞的執行緒的方法。
在head存在並且狀態為 waitStatus時,會通過CAS將狀態變為0,並且喚醒阻塞的執行緒。

相關文章