本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫
並發編程中常遇到這種情況,一個線程需要等待另外多個線程執行後再執行。遇到這種情況你一般怎麼做呢?今天就介紹一種JDk提供的解決方案來優雅的解決這一問題,那就是倒計時器CountDownLatch。本文將分以下兩部分介紹:
CountDownLatch的使用CountDownLatch源碼分析1. CountDownLatch的使用
CountDownLatch的作用是讓線程等待其它線程完成一組操作後才能執行,否則就一直等待。
舉個開會的例子:
老闆先進入會議室準備材料等待5個員工陸續進入會議室員工到齊開始開會老闆不能一來就開會,必須要等員工都到了再開會,用CountDownLatch實現如下:
public class CountDownLatchTest {private static CountDownLatch countDownLatch = new CountDownLatch(5); // Boss線程,等待員工到齊開會 static class BossThread extends Thread { @Override public void run() { System.out.println("Boss進入會議室準備材料..."); System.out.println("Boss在會議室等待..."); try { countDownLatch.await(); // Boss等待 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Boss等到人齊了,開始開會..."); } } // 員工到達會議室 static class EmpleoyeeThread extends Thread { @Override public void run() { System.out.println("員工" + Thread.currentThread().getName() + ",到達會議室...."); countDownLatch.countDown(); } } public static void main(String[] args) { // Boss線程啟動 new BossThread().start(); // 員工到達會議室 for (int i = 0; i < countDownLatch.getCount(); i++) { new EmpleoyeeThread().start(); } }}
控制臺輸出:
Boss進入會議室準備材料...Boss在會議室等待...員工Thread-2,到達會議室....員工Thread-3,到達會議室....員工Thread-4,到達會議室....員工Thread-1,到達會議室....員工Thread-5,到達會議室....Boss等到人齊了,開始開會...
總結CountDownLatch的使用步驟:(比如線程A需要等待線程B和線程C執行後再執行)
創建CountDownLatch對象,設置要等待的線程數N(這裡是2);等待線程A調用await()掛起;線程B執行後調用countDown(),使N-1;線程C執行後調用countDown(),使N-1;調用countDown()後檢查N=0了,喚醒線程A,在await()掛起的位置繼續執行。2. CountDownLatch源碼分析
CountDownLatch是通過一個計數器來實現的,當我們在new 一個CountDownLatch對象的時候需要帶入該計數器值,該值就表示了線程的數量。每當一個線程完成自己的任務後,計數器的值就會減1。當計數器的值變為0時,就表示所有的線程均已經完成了任務,然後就可以恢復等待的線程繼續執行了。
2.1 類結構
CountDownLatch只有一個屬性Sync,Sync是繼承了AQS的內部類。
創建CountDownLatch時傳入一個count值,count值被賦值給
AQS.state
。
CountDownLatch是通過AQS共享鎖實現的,AQS這篇文章中詳細講解了AQS獨佔鎖的原理,AQS共享鎖和獨佔鎖原理只有很細微的區別,這裡大致介紹下:
線程調用acquireSharedInterruptibly()方法獲取不到鎖時,線程被構造成結點進入AQS阻塞隊列。當有線程調用releaseShared()方法將當前線程持有的鎖徹底釋放後,會喚醒AQS阻塞隊列中等鎖的線程,如果AQS阻塞隊列中有連續N個等待共享鎖的線程,就將這N個線程依次喚醒。
public class CountDownLatch {private static final class Sync extends AbstractQueuedSynchronizer { Sync(int count) { setState(count); } } private final Sync sync; public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); }}
2.2 await()
await()
是將當前線程阻塞,理解
await()
的原理就是要弄清楚await()是如何將線程阻塞的。
await()
調用的就是AQS獲取共享鎖的方法。當
AQS.state=0
時才能獲取到鎖,由於創建CountDownLatch時設置了
state=count
,此時是獲取不到鎖的,所以調用
await()
的線程掛起並構造成結點進入AQS阻塞隊列。
創建CountDownLatch時設置AQS.state=count,可以理解成鎖被重入了count次。await()方法獲取鎖時鎖被佔用了,只能阻塞。
/*** CountDownLatch.await()調用的就是AQS獲取共享鎖的方法acquireSharedInterruptibly() */public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1);}/** * 獲取共享鎖 * 如果獲取鎖失敗,就將當前線程掛起,並將當前線程構造成結點加入阻塞隊列 * 判斷是否獲取鎖成功的方法由CountDownLatch的內部類Sync實現 */public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0) // 嘗試獲取鎖的方法由CountDownLatch的內部類Sync實現 doAcquireSharedInterruptibly(arg); // 獲取鎖失敗,就將當前線程掛起,並將當前線程構造成結點加入阻塞隊列}/** * CountDownLatch.Sync實現AQS獲取鎖的方法 * 只有AQS.state=0時獲取鎖成功。 * 創建CountDownLatch時設置了state=count,調用await()時state不為0,返回-1,表示獲取鎖失敗。 */protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1;}
2.3 countDown()
countDown()
方法是將count-1,如果發現count=0了,就喚醒阻塞的線程。
countDown()
調用AQS釋放鎖的方法,每次將state減1。當state減到0時是無鎖狀態了,就依次喚醒AQS隊列中阻塞的線程來獲取鎖,繼續執行邏輯代碼。
/*** CountDownLatch.await()調用的就是AQS釋放共享鎖的方法releaseShared() */public void countDown() { sync.releaseShared(1);}/** * 釋放鎖 * 如果鎖被全部釋放了,依次喚醒AQS隊列中等待共享鎖的線程 * 鎖全部釋放指的是同一個線程重入了N次需要N次解鎖,最終將state變回0 * 具體釋放鎖的方法由CountDownLatch的內部類Sync實現 */public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { // 釋放鎖,由CountDownLatch的內部類Sync實現 doReleaseShared(); // 鎖全部釋放之後,依次喚醒等待共享鎖的線程 return true; } return false;}/** * CountDownLatch.Sync實現AQS釋放鎖的方法 * 釋放一次,將state減1 * 如果釋放之後state=0,表示當前是無鎖狀態了,返回true */protected boolean tryReleaseShared(int releases) { for (;;) { int c = getState(); if (c == 0) return false; // state每次減1 int nextc = c-1; if (compareAndSetState(c, nextc)) return nextc == 0;// state=0時,無鎖狀態,返回true }}
總結
CountDownLatch用於一個線程A需要等待另外多個線程(B、C)執行後再執行的情況。
創建CountDownLatch時設置一個計數器count,表示要等待的線程數量。線程A調用
await()
方法後將被阻塞,線程B和線程C調用
countDown()
之後計數器count減1。當計數器的值變為0時,就表示所有的線程均已經完成了任務,然後就可以恢復等待的線程A繼續執行了。
CountDownLatch是由AQS實現的,創建CountDownLatch時設置計數器count其實就是設置
AQS.state=count
,也就是重入次數。
await()
方法調用獲取鎖的方法,由於
AQS.state=count
表示鎖被佔用且重入次數為count,所以獲取不到鎖線程被阻塞並進入AQS隊列。
countDown()
方法調用釋放鎖的方法,每釋放一次AQS.state減1,當AQS.state變為0時表示處於無鎖狀態了,就依次喚醒AQS隊列中阻塞的線程來獲取鎖,繼續執行邏輯代碼。