併發測試輔助類CountDownLatch使用與源碼

  • CountDownLatch類介紹:

    一個同步輔助類,在完成一組正在其他線程中執行的操作之前,它允許一個或多個線程一直等待。

用給定的計數 初始化 CountDownLatch。由於調用了 countDown() 方法,所以在當前計數到達零之前,await 方法會一直受阻塞。之後,會釋放所有等待的線程,await 的所有後續調用都將立即返回。這種現象只出現一次——計數無法被重置。如果需要重置計數,請考慮使用 CyclicBarrier。

CountDownLatch 是一個通用同步工具,它有很多用途。將計數 1 初始化的 CountDownLatch 用作一個簡單的開/關鎖存器,或入口:在通過調用 countDown() 的線程打開入口前,所有調用 await 的線程都一直在入口處等待。用 N 初始化的 CountDownLatch 可以使一個線程在 N 個線程完成某項操作之前一直等待,或者使其在某項操作完成 N 次之前一直等待。

CountDownLatch 的一個有用特性是,它不要求調用 countDown 方法的線程等到計數到達零時才繼續,而在所有線程都能通過之前,它只是阻止任何線程繼續通過一個 await。

await()方法:
throws InterruptedException
使當前線程在鎖存器倒計數至零之前一直等待,除非線程被 中斷。
如果當前計數爲零,則此方法立即返回。

如果當前計數大於零,則出於線程調度目的,將禁用當前線程,且在發生以下兩種情況之一前,該線程將一直處於休眠狀態:

由於調用 countDown() 方法,計數到達零;或者
其他某個線程中斷當前線程。

countDown()方法:
遞減鎖存器的計數,如果計數到達零,則釋放所有等待的線程。
如果當前計數大於零,則將計數減少。如果新的計數爲零,出於線程調度目的,將重新啓用所有的等待線程。

如果當前計數等於零,則不發生任何操作。

綜合上述所的:也就是當countDown()方法的值減爲0的時候,取消阻塞;否則調用await()會被一隻阻塞,下面是測試代碼:

package com.example.demo;/**
 * Created by wusong on 18/8/27.
 */

import java.util.concurrent.CountDownLatch;

public class Etest implements Runnable {

    static final CountDownLatch startSignal = new CountDownLatch(1);//拉粑粑信號槍
    static final CountDownLatch donesSignal = new CountDownLatch(10);//排隊拉粑粑的人
    static int k =100;



    @Override
    public void run() {
        try {
            startSignal.await();//把拉粑粑的人堵在廁所門口,直至countDown爲0
            System.out.println("我是"+Thread.currentThread().getName()+"我搶到了拉粑粑權,很開心!");
            donesSignal.countDown();//哦耶,我拉完了。撤了~
            System.out.println("我是"+Thread.currentThread().getName()+"我拉完了");

        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) throws Exception {
        //創建憋粑粑的人
        for (int i = 0;i < 10;i++)
            new Thread(new Etest(),"拉粑粑能手 NO."+(1+i)).start();
        startSignal.countDown();//把所有被堵在廁所門口要拉粑粑的人放出來~
        donesSignal.await();
    }

}

然後我們來看這個類是如何具體實現對所有線程進行阻塞以方便我們測試的;
首先是構造函數:

    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

一隻點點點,最後到了AbstractQueuedSynchronizer類中的setState方法(依賴AQS實現的)將我們上述傳入的count設置爲了state

    protected final void setState(int newState) {
        state = newState;
    }
  • 先來看countDown()方法:
    public void countDown() {
        sync.releaseShared(1);
    }

AbstractQueuedSynchronizer類中的releaseShared方法:

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

CountDownLatch具體實現的tryReleaseShared(arg):

        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                //爲0返回false
                if (c == 0)
                    return false;
                    //每調用一次減1
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
  • 再來看await()方法,也就是爲啥能阻塞俺們所有的線程:
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

點進去來到了AbstractQueuedSynchronizer類中的,這是我們就發現這玩意是基於共享鎖實現的,搜呆斯內:

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

tryAcquireShared(arg) 的具體實現:

protected int tryAcquireShared(int acquires) {
			//getState()就是我們之前構造函數設置的值,如果減爲0的話返回1否則返回-1
            return (getState() == 0) ? 1 : -1;
        }

然後來到doAcquireSharedInterruptibly(int arg),參數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 (p == head) {
                //重複之前的操作,如果之前的操作,這時候我們就回判斷state是否爲0,
                //以爲同時也會有其他線程執行countDown()方法,爲了保證原子性的操作得從新獲取一次。
                    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);
        }
    }

具體的釋放操作:

    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node);
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            //因爲當前隊列就一個節點,所以後續節點爲空。直接釋放
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章