Java多執行緒同步工具類之CountDownLatch

bigfan發表於2019-06-28

在過去我們實現多執行緒同步的程式碼中,往往使用join()、wait()、notiyAll()等執行緒間通訊的方式,隨著JUC包的不斷的完善,java為我們提供了豐富同步工具類,官方也鼓勵我們使用工具類來實現多執行緒的同步,今天我們就對其中CountDownLatch類的使用與底層實現進行分析與總結。

一、CountDownLatch使用

CountDownLatch其實可以看做一個計數器,統計多個執行緒執行完成的情況,適用於控制一個或多個執行緒等待,直到所有執行緒都執行完畢的場景,類似與Thread.join()的作用。下面我們通過一個簡單的例子看下CountDownLatch的使用。

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

        // 啟動計數執行緒
        for (int i = 0; i < 5; i++) {
            new CountDownLatchThread(i, countDownLatch).start();
        }

        // 啟動等待執行緒
        for (int i = 0; i < 5; i++) {
            new Thread() {
                public void run() {

                    try {
                        countDownLatch.await();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }

                    System.out.println("計數完畢了," + Thread.currentThread().getName() + "等待執行緒執行");

                }
            }.start();
        }

    }

計數執行緒程式碼:

public class CountDownLatchThread extends Thread {

    private CountDownLatch countDownLatch;

    private int name;

    private int count;

    public CountDownLatchThread(int name, CountDownLatch countDownLatch) {
        this.name = name;
        this.countDownLatch = countDownLatch;
        this.count = 0;
    }

    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                Thread.sleep(100);
                count++;
            }
            System.out.println(name + "號執行緒--" + Thread.currentThread().getName() + "--計數完成了");
            countDownLatch.countDown();

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

輸出結果:

1號執行緒--Thread-1--計數完成了
0號執行緒--Thread-0--計數完成了
4號執行緒--Thread-4--計數完成了
2號執行緒--Thread-2--計數完成了
3號執行緒--Thread-3--計數完成了
計數完畢了,Thread-5等待執行緒執行
計數完畢了,Thread-6等待執行緒執行
計數完畢了,Thread-7等待執行緒執行
計數完畢了,Thread-8等待執行緒執行
計數完畢了,Thread-9等待執行緒執行

通過上面的例子可以看到,利用CountDownLatch的countDown方法與await()方法,我們可以同步計數執行緒與等待執行緒,使等待執行緒在所有計數執行緒完成後再開始執行。

二、CountDownLatch原始碼分析

接下來我們對countDownLatch內部原始碼進行一下分析。

1、CountDownLatch的構造。

首先看下CountDownLatch的建構函式

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

CountDownLatch的建構函式會接收一個count值做為計數器,也就是如果你需要等待N個執行緒執行結束,那這裡就傳入N。同時CountDownLatch會例項化一個Sync物件,這個Sync其實是CountDownLatch內部定義的一個繼承自AbstractQueuedSynchronizer的實現類,所以CountDownLatch提供的同步和其他功能都是圍繞Sync這個子類實現的,也就是基於AbstractQueuedSynchronizer類來實現的。

我們來看下Sync這個類的定義

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

        /**
        1、設定AbstractQueuedSynchronizer中同步狀態的值state,也就是計數器的值。
        2、這個值volatile變數,必須保證執行緒間的可見性;
        **/
        Sync(int count) {
            setState(count);
        }

        int getCount() {
            return getState();
        }

        //獲取同步狀態的值
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

        protected boolean tryReleaseShared(int releases) {
            // 通過CAS操作改變同步狀態值,保證同步狀態的值state的執行緒安全。
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }

2、countDown方法

首先我們看下countDown方法的原始碼

    
    public void countDown() {
       //改變同步狀態值,執行緒執行完成時計數器減一
        sync.releaseShared(1);
    }

 AbstractQueuedSynchronizer類中releaseShared() 方法的原始碼

    public final boolean releaseShared(int arg) {
        // CountDownLatch定義的子類Sync實現,通過CAS操作改變State的值
        if (tryReleaseShared(arg)) {
            //State以遞減為0,代表著所有執行執行緒執行完畢,共享模式下釋放鎖,那麼等待執行緒就能夠拿到鎖往下執行。
            doReleaseShared();
            return true;
        }
        return false;
    }

 

當呼叫CountDownLatch的countDown方法時,就會執行計數器進行減一操作,直到所有執行緒全部執行完畢,計算器為0時喚醒等待執行緒。

AbstractQueuedSynchronizer中doReleaseShared方法是執行共享模式下釋放鎖的操作,從而讓等待執行緒獲取鎖,繼續向下執行。

3、await方法

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

 AbstractQueuedSynchronizer類中acquireSharedInterruptibly() 方法的原始碼

    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        //獲取同步狀態值
        if (tryAcquireShared(arg) < 0)
            //同步狀態值即計數器的值不為0,等待執行緒共享模式下嘗試獲取鎖,獲取不到鎖的話進入阻塞
            doAcquireSharedInterruptibly(arg);
    }

await方法的實現也很明確,首頁獲取同步狀態也就是計數器的值,如果為0即所有執行緒執行完畢返回1,否則返回-1的話,等待執行緒在共享模式下嘗試獲取鎖,獲取不到鎖的話進入阻塞;

AbstractQueuedSynchronizer中doAcquireSharedInterruptibly方法是執行共享模式下獲取鎖的操作;

三、總結

通過上面分析可以看到CountDownLatch是基於AbstractQueuedSynchronizer類實現的,一個非常實用的多執行緒控制工具類,它類似與一個計數器用來控制指定的執行緒等待,直到計數器歸零。以上我們對CountDownLatch類的使用與核心方法的原始碼進入了一定的分析,其中如有不足與不正確的地方還望指出與海涵。

 

關注微信公眾號,檢視更多技術文章。

 

相關文章