java多線程之CountDownLatch源碼解析

前言

本篇開始分析CountDownLatch的源碼,分析結束後,會用一個示例展示CountDownLatch的應用場景。

1、簡介

CountDownLatch本質上是一個共享鎖,內部維護了一個Sync,Sync是繼承AQS抽象類的,它沒有公平和不公平之分,含義是允許一個或多個線程等待其它線程的操作執行完畢後再執行後續的操作。

2、分析源碼

2.1、函數列表

//構造一個內部維護給定計數器的CountDownLatch
CountDownLatch(int count)
// 等待,直到CountDownLatch內部計數器爲0
void await()
// 在指定時間內等待CountDownLatch內部計數器爲0
boolean await(long timeout, TimeUnit unit)
// CountDownLatch的內部計數器減1
void countDown()
// 返回當前計數。
long getCount()
String toString()

2.2、分析await方法

這個是CountDownLatch中的await方法

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

acquireSharedInterruptibly方法的實現在AQS中,源碼如下:

public final void acquireSharedInterruptibly(int arg)
    throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    //嘗試獲取共享鎖,成功返回
    if (tryAcquireShared(arg) < 0)
    	//如果失敗則進入AQS的隊列中排隊等待被喚醒
        doAcquireSharedInterruptibly(arg);
}

tryAcquireShared在內部類Sync中重寫了,源碼如下:

protected int tryAcquireShared(int acquires) {
	//state(內部維護的計數器)爲0,則獲取共享鎖成功,否則就排隊
	return (getState() == 0) ? 1 : -1;
}

doAcquireSharedInterruptibly方法是AQS共享鎖排隊重試的方法,之前ReentrantReadWriteLock和Semaphore類都有講過,不再重複。

2.3、分析countDown方法

這個是CountDownLatch中的countDown方法

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

releaseShared方法在AQS中,源碼如下:

public final boolean releaseShared(int arg) {
	// 嘗試釋放共享鎖,如果成功了,就喚醒排隊的線程
	if (tryReleaseShared(arg)) {
       doReleaseShared();
        return true;
    }
    return false;
}

tryReleaseShared的方法在CountDownLatch的內部類Sync中,源碼如下:

protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
     for (;;) {
     	 //獲取計數器值
         int c = getState();
         //爲0就返回
         if (c == 0)
             return false;
         //減1
         int nextc = c-1;
         //CAS更新值,計數器值由1變爲0的時候才返回true
         if (compareAndSetState(c, nextc))
             return nextc == 0;
     }
 }

doReleaseShared方法在AQS中實現,作用是依次喚醒CLH隊列中排隊的線程,此方法之前也分析過,不再重複。

3、示例

下面的代碼的業務邏輯,比如下單業務:

  1. 主線程就緒
  2. 3個線程開始處理業務
  3. 業務邏輯處理完,主線程繼續
public class CountDownLatchTest {

    public static final CountDownLatch WORK_THREAD = new CountDownLatch(3);
    public static final CountDownLatch MAIN_THREAD = new CountDownLatch(1);

    public static void main(String[] args) throws Exception{
        for (int i=0; i<3; i++){
            new Thread(() -> { method(); }).start();
        }
        //主線程邏輯
        Thread.sleep(2000);
        System.out.println("主線程準備就緒……");
        MAIN_THREAD.countDown();
        WORK_THREAD.await();
        System.out.println("業務處理完,主線程繼續……");
    }

    public static void method(){
        System.out.println("waiting業務的線程名稱是:" + Thread.currentThread().getName());
        try {
            //處理業務
            MAIN_THREAD.await();
            Thread.sleep(100);
            System.out.println("finish業務的線程名稱是:" + Thread.currentThread().getName());
            WORK_THREAD.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

輸出結果:

waiting業務的線程名稱是:Thread-2
waiting業務的線程名稱是:Thread-1
waiting業務的線程名稱是:Thread-0
主線程準備就緒……
finish業務的線程名稱是:Thread-1
finish業務的線程名稱是:Thread-2
finish業務的線程名稱是:Thread-0
業務處理完,主線程繼續……

結束語

上面示例中,主線程維護的CountDownLatch的wait()調用了3次,countDown()調用了1次,countDown()方法將state減到0後,之前排隊的3個wait()方法阻塞的線程都被喚醒繼續執行。

寫到這裏,看完我前面分析ReentrantReadWriteLock和Semaphore的同學應該更加理解了CountDownLatch中爲什麼使用共享鎖而不是互斥鎖。

共享鎖的釋放是一個接一個的通知隊列中排隊的線程,而互斥鎖的釋放是一次只通知一個

如果我的分析對你有幫助,請點個贊再走,謝謝大家!

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