發編程系列(十五) CountDownLatch閉鎖

鎖和信號量是控制併發,但是CountDownLatch(倒數計數器)是提供併發(讓線程一起等待到某個條件一起觸發執行),個人感覺CountDownLatch在平常工作環境並不常用,最常見的場景就是開啓多個線程同時執行某個任務,等所有任務執行完成後再做結果彙總;在countdownLatch出現之前都是使用join()方法來實現,但是join不夠靈活,不能夠滿足不同場景下的需求·

1.1 Sync內部類源碼分析

    //Sync繼承 AQS使用起state屬性來作爲count
    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;
        //構造器
        Sync(int var1) {
            //設置state的值爲count
            this.setState(var1);
        }
       //獲取count的值,及state的值
        int getCount() {
            return this.getState();
        }
        //重寫父類tryAcquireShared的方法
        protected int tryAcquireShared(int var1) {、
            //如果state值爲0;返回1否則返回-1
            return this.getState() == 0 ? 1 : -1;
        }
        //重寫 父類tryReleaseShared方法
        protected boolean tryReleaseShared(int var1) {

             for (;;) {
                //獲取state的值
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                //cas更新成功
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }

        }
    }

1.2CountDownLatch 構造函數

    public CountDownLatch(int count) {
        //構造函數傳入的值必須大於0;否則拋出異常
        if (count < 0) throw new IllegalArgumentException("count < 0");
        //創建一個Sync對象
        this.sync = new Sync(count);
    }

1.3await() 方法解析

當前線程調用了await()方法後會,會將當前線程阻塞直到出現下面兩種情況之一纔會返回:

當所有線程調用都調用了countDown方法後,也就是說調用了await方法的都要在調用countDown方法一遍使計數器的值爲0

其他線程調用了當前線程的interrupt方法中斷了當前線程, 當前線程會拋出interruptedException異常返回

    public void await() throws InterruptedException {
        //sync調用父類的acquireSharedInterruptibly方法
        sync.acquireSharedInterruptibly(1);
    }
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
         //如果線程被中斷,則拋出異常
        if (Thread.interrupted())
            throw new InterruptedException();
        //如果tryAcquireShared小於0,則進入AQS同步隊列
        if (tryAcquireShared(arg) < 0)
            //調用AQS的方法進入同步隊列
            doAcquireSharedInterruptibly(arg);
    }

Sync類的tryAcquireShared方法在state等於0時返回1;否則返回-1

回到AQS的AcquireSharedInterruptibly方法,當Sync類的tryAcquireShared返回1則回到AQS的AcquireSharedInterruptibly方法返回,即await方法返回;

1.4 await(long timeout,TimeUnit unit)源碼分析

    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

AQS中的tryAcquireSharedNanos方法

    public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquireShared(arg) >= 0 ||
            doAcquireSharedNanos(arg, nanosTimeout);
    }

AQS中的doAcquireSharedNanos方法

    private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        final long deadline = System.nanoTime() + nanosTimeout;
        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 true;
                    }
                }
                nanosTimeout = deadline - System.nanoTime();
                if (nanosTimeout <= 0L)
                    return false;
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

當線程調用了countDownLacth方法後,當前線程會被阻塞,直到下面的情況之一發生纔會返回

當前線程都調用了countDownLacth對象的countDown方法,也就是計數器爲0的時候,這個時候返回true

設置timeout時間到了,因爲超時兒返回false

其他線程調用了當前線程的interrupt方法中斷了當前線程,當前線程會拋出InterruptException異常後返回

2. countDown()方法

當前線程調用了該方法後悔遞減計數器的值,遞減後如果計數器爲0,則會喚醒await方法而被阻塞的線程,否則什麼都不做

    public void countDown() {
        sync.releaseShared(1);
    }

AQS中的releaseShared方法

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
tryReleaseShared方法需要子類實現;具體實現在CountDownLatch內部類Sync中的tryReleaseShared方法

AQS中的doReleaseShared 方法:

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

 

 

 

發佈了59 篇原創文章 · 獲贊 3 · 訪問量 7998
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章