02.06 多線程之CountDownLatch的用法及原理筆記

前言-CountDownLatch是什麼?


CountDownLatch是具有synchronized機制的一個工具,目的是讓一個或者多個線程等待,直到其他線程的一系列操作完成。

CountDownLatch初始化的時候,需要提供一個整形數字,數字代表著線程需要調用countDown()方法的次數,當計數為0時,線程才會繼續執行await()方法後的其他內容。CountDownLatch(int count);


對象中的方法


<code>getCount:
返回當前的計數count值,/<code>


<code>public void countDown()
調用此方法後,會減少計數count的值。
遞減後如果為0,則會釋放所有等待的線程/<code>


<code>public void await()
throws InterruptedException
調用CountDownLatch對象的await方法後。
會讓當前線程阻塞,直到計數count遞減至0。/<code>

如果當前線程數大於0,則當前線程在線程調度中將變得不可用,並處於休眠狀態,直到發生以下兩種情況之一:

1、調用countDown()方法,將計數count遞減至0。

2、當前線程被其他線程打斷。


<code>public boolean await(long timeout,
TimeUnit unit)
throws InterruptedException/<code>

同時await還提供一個帶參數和返回值的方法。

如果計數count正常遞減,返回0後,await方法會返回true並繼續執行後續邏輯。

或是,尚未遞減到0,而到達了指定的時間間隔後,方法返回false。

如果時間小於等於0,則此方法不執行等待。


實際案例


join阻塞等待線程完成

首先建立3個線程。

<code>public class Worker1 implements Runnable {
@Override
public void run() {
System.out.println("-線程1啟動");

try {
Thread.sleep(13_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程1完成--我休眠13秒\\r\\n");
}
}

public class Worker2 implements Runnable {
@Override
public void run() {
System.out.println("-線程2啟動");
try {
Thread.sleep(3_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程2完成--我休眠3秒\\r\\n");
}
}

public class Worker3 implements Runnable {
@Override
public void run() {
System.out.println("-線程3啟動");
try {
Thread.sleep(3_000);
} catch (InterruptedException e) {
e.printStackTrace();
}


try {
Thread.sleep(3_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程3完成--我休眠6秒\\r\\n");
System.out.println();
}
}


public class Main {
public static void main(String[] args) throws InterruptedException {
Worker1 worker1 = new Worker1();

Worker2 worker2 = new Worker2();
Worker3 worker3 = new Worker3();

Thread thread1 = new Thread(worker1,"線程1");
Thread thread2 = new Thread(worker2,"線程2");
Thread thread3 = new Thread(worker3,"線程3");

thread1.start();
thread2.start();
thread3.start();

thread1.join();
thread2.join();
thread3.join();
System.out.println("主線程結束....");

}
}/<code>

打印結果如下:

<code>-線程3啟動
-線程2啟動
-線程1啟動
線程2完成--我休眠3秒
線程3完成--我休眠6秒

線程1完成--我休眠13秒

主線程結束....
Process finished with exit code 0/<code>

可以看出三個線程是並行執行的。啟動順序,並不和執行完畢的順序一致,但可以明確的是,主線程為一直阻塞,直到三個線程執行完畢。


CountDownLatch用法

阿里巴巴的數據庫連接池Druid中也用了countDownLatch來保證初始化。

多線程之CountDownLatch的用法及原理筆記

<code>// 開啟創建連接的線程,如果線程池createScheduler為null,
//則開啟單個創建連接的線程
createAndStartCreatorThread();

// 開啟銷燬過期連接的線程
createAndStartDestroyThread(); /<code>


自己編寫一個例子:這裡模擬一種情況:主線程 依賴 線程A初始化三個數據,才能繼續加載後續邏輯。

多線程之CountDownLatch的用法及原理筆記


<code>public class CountDownArticle {
/**
* 模擬 主線程 依賴 線程A初始化一個數據,才能繼續加載後續邏輯
*/
public static void main(String[] args) throws InterruptedException {
AtomicReference<string> key = new AtomicReference<>("");
CountDownLatch countDownLatch = new CountDownLatch(3);
Thread t = new Thread(() -> {
try {

//休眠5秒,模擬數據的初始化
TimeUnit.SECONDS.sleep(5);

key.set("核心秘鑰123456");
System.out.println("數據1初始化完畢");

//釋放---此處可以在任何位置調用,很靈活
countDownLatch.countDown();

System.out.println("數據2初始化完畢");
countDownLatch.countDown();

System.out.println("數據3初始化完畢");
countDownLatch.countDown();

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

});
t.start();

//等待數據初始化,阻塞
countDownLatch.await();
System.out.println("key:" + key.get());
}

}/<string>/<code>

打印內容如下:

<code>數據1初始化完畢
數據2初始化完畢
數據3初始化完畢
key:核心秘鑰123456/<code>


CountDownLatch和Join用法的區別?

在使用join()中,多個線程只有在執行完畢之後歐才能被解除阻塞,而在CountDownLatch中,線程可以在任何時候任何位置調用countdown方法減少計數,通過這種方式,我們可以更好地控制線程的解除阻塞,而不是僅僅依賴於連接線程的完成。

join()方法的執行邏輯如下圖所示:

多線程之CountDownLatch的用法及原理筆記


原理

從源碼可以看出,CountDownLatch是依賴於AbstractQueuedSynchronizer來實現這一系列邏輯的。

隊列同步器AbstractQueuedSynchronizer是一個用來構建鎖和同步器的框架,它在內部定義了一個被標識為volatile的名為state的變量,用來表示同步狀態。

多個線程之間可以通過AQS來獨佔式或共享式的搶佔資源。

並且它通過內置的FIFO隊列來完成線程的排隊工作。

多線程之CountDownLatch的用法及原理筆記

CountDownLatch中的Sync會優先嚐試修改state的值,來獲取同步狀態。例如,如果某個線程成功的將state的值從0修改為1,表示成功的獲取了同步狀態。 這個修改的過程是通過CAS完成的,所以可以保證線程安全。

反之,如果修改state失敗,則會將當前線程加入到AQS的隊列中,並阻塞線程。


總結

CountDownLatch(int N) 中的計數器,可以讓我們支持最多等待N個線程的操作完成,或是一個線程操作N次。

如果僅僅只需要等待線程的執行完畢,那麼join可能就能滿足。但是如果需要靈活的控制線程,使用CountDownLatch。


注意事項

countDownLatch.countDown();

這一句話儘量寫在finally中,或是保證此行代碼前的邏輯正常運行,因為在一些情況下,出現異常會導致無法減一,然後出現死鎖。


CountDownLatch 是一次性使用的,當計數值在構造函數中初始化後,就不能再對其設置任何值,當 CountDownLatch 使用完畢,也不能再次被使用。


寫在最後

為了方便大家學習討論,我創建了一個java疑難攻堅互助大家庭,和其他傳統的學習交流不同。本群主要致力於解決項目中的疑難問題,在遇到項目難以解決的問題時,都可以在這個大家庭裡尋求幫助。

公眾號回覆【問題的答案】進入:java中Integer包裝類的基本數據類型是?如果你也經歷過遇到項目難題,無從下手,他人有可能可以給你提供一些思路和看法,一百個人就有一百種思路,同樣,如果你也樂於幫助別人,那解決別人遇到的問題,也同樣對你是一種鍛鍊。


自學路上你不孤單,歡迎來公眾號【俠夢的開發筆記】,回覆乾貨,為你準備了精選的學習視頻



分享到:


相關文章: