Java併發程式設計-CountDownLatch
基於AQS的前世今生,來學習併發工具類CountDownLatch。本文將從CountDownLatch的應用場景、原始碼原理解析來學習這個併發工具類。
1、應用場景
CountDownLatch是併發包中用來控制一個或者多個執行緒等待其他執行緒完成操作的併發工具類。現以工作中的一個場景來描述下CountDownLatch的應用,程式碼如下:
/* 模擬工作中的一個需求場景: 使用者會選擇多個演算法來計算費用,最後會將所有演算法計算出的費用做一個加權求平均數,這個平均數是最終的費用。 每個演算法的複雜度都不一樣,打算每個執行緒負責一個演算法的實現,所有的執行緒執行完成,最後再求平均數。 1、為每個演算法建立一個執行緒,每個執行緒負責一個演算法的實現 2、通過CountDownLatch來控制所有演算法執行緒的同步 3、全部計算完成後再求平均數 */ public class CountDownLatchTask { public static void main(String[] args) { CountDownLatchTask countDownLatchTask = new CountDownLatchTask(); countDownLatchTask.startThreads(5); } //根據執行緒數和選擇的演算法 排程演算法對應的實現 private void startThreads(int threadNumber) { CountDownLatch countDownLatch = new CountDownLatch(threadNumber); for (int i = 0; i < threadNumber; i++) { new Thread(new Runnable() { @Override public void run() { System.out.println("執行緒演算法實現:" + Thread.currentThread().getName()); countDownLatch.countDown(); } }).start(); } try { countDownLatch.await(); System.out.println("加權求平均數"); } catch (InterruptedException e) { e.printStackTrace(); } } }
在分析原理實現前,總結下CountDownLatch的作用就是阻塞其他執行緒直到條件允許後才釋放該阻塞,除了上述這個小案例,實際工作中還有很多可以使用CountDownLatch的場景,比如解析Excel檔案時可以同時解析多個Sheet頁,所有的Sheet解析完成才算完成了Excel檔案的解析。從這個程式碼中也可以看到CountDownLatch的主要方法就是await和countDown,下面將以這兩個方法來分析下CountDownLatch的原理實現。
2、原始碼原理解析
2.1 await方法
呼叫await方法會阻塞當前執行緒直到計數器的數值為0,方法如下:
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); //共享式獲取AQS的同步狀態 }
呼叫的是AQS的acquireSharedInterruptibly方法:
public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted())//執行緒中斷 說明閉鎖對執行緒中斷敏感 throw new InterruptedException(); if (tryAcquireShared(arg) < 0) //閉鎖未使用完成 執行緒進入同步佇列自旋等待 doAcquireSharedInterruptibly(arg); }
其中tryAcquireShared依賴的是Sync的實現,和之前的ReentrantLock、ofollow,noindex">ReentrantReadWriteLock 及Semaphore相比,CountDownLatch的Sync只提供了一種方式,程式碼如下:
protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; //AQS的同步狀態為0則閉鎖結束 可以進行下一步操作 }
doAcquireSharedInterruptibly方法就不再贅述,和之前Semaphore的實現是一致的,本質上仍然是AQS同步佇列的入隊自旋等待。
2.2 countDown方法
呼叫countDown方法會將計數器的數值減1直到計數器為0,方法如下:
public void countDown() { sync.releaseShared(1); }
和Semaphore一樣,呼叫的是AQS的releaseShared方法:
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) {//減少閉鎖的計數器 doReleaseShared();//喚醒後續執行緒節點 return true; } return false; }
其中tryReleaseShared依賴的是Sync的實現,和之前的ReentrantLock、ReentrantReadWriteLock 及Semaphore相比,CountDownLatch的Sync只提供了一種方式,程式碼如下:
protected boolean tryReleaseShared(int releases) { // Decrement count; signal when transition to zero for (;;) { int c = getState(); if (c == 0) return false; //計數器已經是0了 int nextc = c-1; //計數器減1 if (compareAndSetState(c, nextc)) //CAS更新同步狀態 return nextc == 0; } }
喚醒後續執行緒節點的doReleaseShared也不再贅述,和之前Semaphore的實現是一致的。
總結:CountDownLatch類使用AQS同步狀態來表示計數。在await時,所有的執行緒進入同步佇列自旋等待,在countDown時,獲取閉鎖成功的執行緒會減少閉鎖的計數器,同時喚醒後續執行緒取獲取閉鎖,直到await中的計數器為0,獲取到閉鎖的執行緒才可以通過,執行下一步操作。
參考資料: