【Java併發工具】CountDownLatch

目錄

1 前言

2 功能介紹

3 基於AbstractQueuedSynchronizer的實現

3.1 同步器Sync

3.2 構造方法

3.3 await方法

3.4 countDown方法

4 一次性使用


1 前言

本人使用jdk8版本。

CountDownLatch 允許一個或多個線程等待其他線程完成操作,作用與Thread.join()相似,join可以參考:Thread.join()原理

2 功能介紹

在JDK 1.5之後的併發包中提供的 CountDownLatch 也可以實現 join 的功能,並且比join的功能更多,示例如下:

public class CountDownLatchTest {

    static CountDownLatch c=new CountDownLatch(2);

    public static void main(String[] args) throws InterruptedException{
        new Thread(new Runnable(){
            @Override
            public void run(){
            System.out.println(1);
            c.countDown();
            System.out.println(2);
            c.countDown();
        }
     }).start();

     c.await();
     System.out.println("3");
    }
}

上面代碼輸出結果爲1,2,3。main線程會一直阻塞在c.await()上,直到線程中兩次c.countDown()使構造方法中傳入的2變成0。

CountDownLatch 的構造函數接收一個int 類型的參數作爲計數器,如果你想等待N個點完成,這裏就傳入N

當我們調用 CountDownLatchcountDown 方法時,N 就會 -1CountDownLatchawait 方法會阻塞當前線程,直到 N 變成零
由於 countDown 方法可以用在任何地方,所以這裏說的 N 個點,可以是 N個線程,也可以是 1個線程裏的N個執行步驟。用在多個線程時,只需要把這個 CountDownLatch 的引用傳遞到線程裏即可。

如果有某個解析 sheet 的線程處理得比較慢,我們不可能讓主線程一直等待,所以可以使用另外一個帶指定時間的 await 方法—— await(long time,TimeUnit unit),這個方法等待特定時間後,就會不再阻塞當前線程。

3 基於AbstractQueuedSynchronizer的實現

CountDownLatch有一個靜態內部類sync,它是AbstractQueuedSynchronizer子類,並針對自己的功能重寫了AbstractQueuedSynchronizer的構造方法、getCount()、tryAcquireShared()、tryReleaseShared()。CountDownLatch.await()調用的是同步器的acquireSharedInterruptibly(),CountDownLatch.countDown()調用的是同步器的releaseShared()。

可以參考:AbstractQueuedSynchronizer共享式同步狀態獲取與釋放

3.1 同步器Sync

    private final Sync sync;

    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

        Sync(int count) {
            setState(count);        // 設置鎖被獲取的個數
        }

        int getCount() {
            return getState();      // 獲取鎖被獲取的個數
        }

        // 若鎖的獲取數位0時獲取成功,否則獲取失敗且當前線程阻塞
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

        // 將鎖的獲取數-1,當減至0時返回true且阻塞的線程會被喚醒,否則false
        protected boolean tryReleaseShared(int releases) {
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }

3.2 構造方法

初始化Sync屬性,同時設置鎖已被獲取的個數。

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

3.3 await方法

acquireSharedInterruptibly()會先調用重寫的tryAcquireShared()來嘗試獲取鎖,若鎖的數量>0,會阻塞在該方法,直到鎖的數量=0將當前線程從該方法喚醒。

    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

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

3.4 countDown方法

countDown()會嘗試調用重寫的tryReleaseShared()來釋放鎖,只有getCount() == 0時返回true,進而執行doReleaseShared()來將阻塞的線程喚醒。

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

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

4 一次性使用

從上面介紹可知,要想使用CountDownLatch完成相關功能,首先要在構造時傳入一個int值,即Sync同步器鎖的狀態,當功能實現後,Sync鎖的狀態肯定是0,否則線程會一直阻塞。CountDownLatch中除了構造方法中可設置Sync鎖的狀態外沒有提供其它任何方法來設置,加上Sync屬性是private的,所以無法從外部訪問。

因此,在使用CountDownLatch後,建議將指向CountDownLatch的引用設爲null,這樣方便GC。

5 阿里面試題

1.countdowlatch和cyclicbarrier的內部原理和用法,以及相互之間的差別(比如countdownlatch的await方法和countDown是怎麼實現的)。

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