CountDownLatch 源碼解讀

什麼是CountDownLatch

CountDownLatch稱之爲閉鎖,它可以使一個或一批線程在閉鎖上等待,等到其他線程執行完相應操作後,閉鎖打開,這些等待的線程纔可以繼續執行。確切的說,閉鎖在內部維護了一個倒計數器。通過該計數器的值來決定閉鎖的狀態,從而決定是否允許等待的線程繼續執行。

常用方法

public CountDownLatch(int count):構造方法,count表示計數器的值,不能小於0,否者會報異常。

public void await() throws InterruptedException:調用await()會讓當前線程等待,直到計數器爲0的時候,方法纔會返回,此方法會響應線程中斷操作。

public boolean await(long timeout, TimeUnit unit) throws InterruptedException:限時等待,在超時之前,計數器變爲了0,方法返回true,否者直到超時,返回false,此方法會響應線程中斷操作。

public void countDown():讓計數器減1

CountDownLatch使用步驟:

  1. 創建CountDownLatch對象
  2. 調用其實例方法 await(),讓當前線程等待
  3. 調用 countDown()方法,讓計數器減1
  4. 當計數器變爲0的時候, 喚醒阻塞在CountDownLatch上的線程(用阻塞這個詞不太嚴格,後續有講)

源碼解析:

1.CountDownLatch 初始化過程,初始化內部計數器(aqs 的status變量)

//創建一個CountDownLatch對象,需要等待四個任務執行
CountDownLatch countDownLatch = new CountDownLatch(4);

 public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
//Sync爲CountDownLatch 的內部類,初始化aqs的status 變量爲 count
//看過筆記上一篇ReentrantLock分析, 對這一幕應該比較數據
     private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

        Sync(int count) {
            setState(count);
        }

        int getCount() {
            return getState();
        }
           //判斷計數器(staus)是否爲0
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }
        //對內部計數器減1,如果減完1等於0 返回true
        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;
            }
        }
    }

2. countDownLatch.await();

//調用的父類 aqs中的 acquireSharedInterruptibly方法
public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

 public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        //判斷中斷狀態
        if (Thread.interrupted())
            throw new InterruptedException();
        //tryAcquireShared該方法爲 內部類sync中的方法,具體實現見上文中sync類
	    //判斷是否所有線程都已經執行完(status==0意味着 當前任務都已經執行完成)
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }
//是不是很熟悉?
//如果該當前線程爲首節點就自旋等待所有任務執行完成,否則就掛起當前線程
//如果自旋的過程中發現任務已經執行完成,就喚醒所有等待在等待隊列上的線程(所有調用await的線程)
  private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        //創建一個等待節點(具體實現請見筆者上篇reentrantlock解析)
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                //如果當前結點是等待隊列中的第一個結點,判斷當前任務是否執行完(status == 0),如果沒有在執行的任務,重置head節點,並喚醒所有等待在 等待隊列的線程(所有調用await的線程)
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                //shouldParkAfterFailedAcquire檢測前置節點是否爲等待的節點,是就直接返回true,執行parkAndCheckInterrupt掛起當前線程,
                //如果當前結點爲首節點,就把當前結點的等待狀態(waitstatus)設爲-1,並返回false,繼續自旋等待
                //具體詳情,見筆者的reentrantLock 解析
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
			//如果該方法因爲某些特殊情況意外的退出(沒有獲取鎖就退出了),那麼就取消當前線程的等待
            if (failed)
                cancelAcquire(node);
        }
    }

3.countDownLatch.countDown() 實現

 public void countDown() {
        sync.releaseShared(1);
    }
//syn父類aqs的方法
 public final boolean releaseShared(int arg) {
        //對內部計數器減1,如果等於0返回true 
        if (tryReleaseShared(arg)) {
            //喚醒所有等待在CountDownLatch上的阻塞線程
            doReleaseShared();
            return true;
        }
        return false;
    }
//喚醒所有等待線程
 private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                //如果當前等待節點=-1,說明該線程被阻塞,喚醒它
                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)                   
                break;
        }
    }

該篇主要講解了CountDownLatch 實現,對CountDownLatch使用場景有疑問的可以參考https://mp.weixin.qq.com/s/WUwuZjgECBgaWujKV6u4CQ該篇博文

文章部分源碼筆者沒有講的很詳細,沒有講到的地方在筆者的ReentrantLock 源碼解讀中可以找到對應的解釋。

對文中有疑問的地方或者寫的不對的地方歡迎交流

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