CountDownLatch源碼的理解

CountDownLatch源碼的理解

設計理念

CountDownLatch對線程的阻塞、釋放是基於隊列+計數器來實現的。

簡單的例子說明

現在有如下代碼:

public static void main(String[] args) {
    final CountDownLatch latch = new CountDownLatch(2);

    new Thread(){
        public void run() {
            try {
                System.out.println("線程1執行");
                Thread.sleep(5000);
                latch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
    }.start();

    new Thread(){
        public void run() {
            try {
                System.out.println("線程2執行");
                Thread.sleep(3000);
                latch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
    }.start();

    new Thread(){
        public void run() {
            try {
                System.out.println("線程3阻塞");
                latch.await();
                System.out.println("線程3繼續執行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
    }.start();

    try {
        Thread.sleep(1000);
        System.out.println("主線程線程阻塞");
        latch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("主線程繼續執行");
}

運行後的效果

線程1執行
線程2執行
線程3阻塞
主線程線程阻塞
線程3繼續執行
主線程繼續執行

結果說明:當latch中的計數器變爲0時,調用await()的線程會繼續執行,繼續執行的順序跟調用await()的先後有關

隊列+計數器的說明

計數器的簡單說明

爲了簡化流程圖,假設只有一個子線程調用了countDown()方法,只有主線程調用到了await()方法
這裏寫圖片描述
上面的流程圖只是說明計數器對各個線程的影響,個人不才,實在是畫不出多線程+隊列的的流程圖。

隊列的簡單說明

每個線程調用await(),會向隊列中添加一個node,拿剛纔的代碼說明:
1. 線程3調用await(),會在head和麪添加一個node1(Head是自動生成的)

Created with Raphaël 2.1.0Headnode1-線程3
  1. 主線程線程調用await(),會在已有的隊列中添加一個node2
Created with Raphaël 2.1.0Headnode1-線程3node2-主線程
  1. 當計數器爲0,各個線程被喚醒的時候,會判斷線程對應node的前一個節點是否是Head,如果是Head,將當前的node設置爲Head,再次喚醒線程。如果不是Head,繼續掛起。對於上述代碼:
    • 假如喚醒線程的時候,主線程首先執行
      這裏寫圖片描述
    • 假如喚醒線程的時候,線程3首先執行
      這裏寫圖片描述

以上這些流程圖只是爲了將單個功能更形象的表現出來,如果要完全理解,還是要回到源碼中看邏輯

代碼註釋

CountDownLatch 裏面的代碼

public class CountDownLatch {

    //AbstractQueuedSynchronizer的具體類,真正控制線程的操作在這裏實現
    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

        //構造方法,設置state,這裏理解爲設置計數器的大小
        Sync(int count) {
            setState(count);
        }

        //獲取state,這裏理解爲獲取計數器的大小
        int getCount() {
            return getState();
        }

        //嘗試獲取所,如果計數器的大小爲0,返回1;
                    //大小不爲0,返回-1
        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)
                    //如果鎖已經全部釋放了,返回false
                    return false;
                int nextc = c-1;
                //通過CAS,更新計數器的大小
                if (compareAndSetState(c, nextc))
                    //釋放一個鎖(也就是計數器-1)以後,如果再也沒有鎖(計數器爲0),返回true,如果還有鎖(計數器不爲0),返回false
                    return nextc == 0;
            }
        }
    }

    private final Sync sync;

    //構造方法,計數器實際上是AbstractQueuedSynchronizer具體類的state
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

    //阻塞線程,等待計數器爲0,
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    //阻塞線程,等待計數器爲0,或者超過給定的時間
    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() + "]";
    }
}

AbstractQueuedSynchronizer中有關await()中的部分代碼

//await()中實際上是調用該方法
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    //處理異常
    if (Thread.interrupted())
        throw new InterruptedException();
    //嘗試獲取鎖,在這裏,是調用CountDownLatch當中AbstractQueuedSynchronizer具體類中重寫的方法
    //計數器大小爲0,返回1;否則爲-1;
    if (tryAcquireShared(arg) < 0)
        //計數器大小不爲0,執行該方法
        doAcquireSharedInterruptibly(arg);
}

//計數器不爲0時,調用該方法
private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    //向隊列中添加一個節點,(如果當前隊列沒有head,先創建head,再添加node)
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        //開啓自旋,如果計數器爲0,會結束自旋
        for (;;) {
            //獲取當前node的前一個node
            final Node p = node.predecessor();
            //判斷當前node是不是第一個node
            if (p == head) {
                //如果是第一個node,查看計數器是否爲0
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    //計數器爲0,說明所有的鎖都釋放了

                    //將當前的node設置爲head,也就是將隊列中第一個node設置爲head,同時也會喚醒掛起的線程
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    //該方執行完畢,調用await()的線程可以繼續執行
                    return;
                }
            }
            //將調用await()的線程掛起
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

AbstractQueuedSynchronizer中有關countDown()中的部分代碼

//countDown()實際上調用的是該方法
public final boolean releaseShared(int arg) {
    //嘗試釋放鎖,在這裏,是調用CountDownLatch當中AbstractQueuedSynchronizer具體類中重寫的方法
    //計數器-1,返回true,計數器大小沒變,返回false
    if (tryReleaseShared(arg)) {
        //計數器大小-1,進入判斷內部

        doReleaseShared();
        return true;
    }
    return false;
}

//計數器-1後,調用該方法
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,是爲了確保Status的狀態一定會修改成功
                    continue;            // loop to recheck cases
                //喚醒掛起的線程,也就是doAcquireSharedInterruptibly()當中的自旋執行一次,可能再次掛起或者結束
                unparkSuccessor(h);
            }
            //喚醒線程後,head值改變的話,更新Status爲PROPAGATE
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                //確保Status一定會修改成功
                continue;                // loop on failed CAS
        }
        //判讀head是否更換(喚醒掛起的線程,有可能將head更新,也可能不更新)
        if (h == head)                   // loop if head changed
            //在這裏,可以粗略的理解爲:如果喚醒線程後,該線程仍然掛起,結束該方法的自旋
            break;

        //如果head改變,繼續自旋,這樣,就會調用compareAndSetWaitStatus(h, 0, Node.PROPAGATE)
    }
}

結語

上文中沒有分析await(long timeout, TimeUnit unit),留給各位Monkey去分析,順便練習下看源碼的能力。
看了這麼多的分析,不要忘了CountDownLatch的作用:
A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.
簡答說明:等待相應的線程結束後再去執行相關的代碼。
源碼作者:Doug Lea

轉載請標明出處:http://blog.csdn.net/qq_26411333/article/details/51416373

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