筆者最近在解析基於AQS的ReentrantLock實現,ReentrantLock是可重入的獨佔鎖,今天解析一下juc包中的共享鎖CountDownLatch實現,僅當筆記。
CountDownLatch是一種共享鎖,區別於獨佔鎖,共享鎖在某一個時刻可能有多個線程同時訪問共享資源,當指定的線程都完成操作後,CountDownLatch才允許後續的操作繼續進行。就好像百米賽跑,只有所有選手都跑完全程,裁判才能記錄選手成績,在此之前,裁判只能等待。
CountDownLatch與ReentrantLock一樣,都是基於AQS實現,AQS基本原理此處不再贅述,想了解的讀者請看我的另一篇文章,本文只解析CountDownLatch的內容。
CountDownLatch的接口如下,
CountDownLatch在初始化時,會設置共享資源數目,該數目一旦設置,無其他接口能進行更改。線程同步時最主要使用的爲await和countDown方法,需要等待其他線程完成共享資源訪問的線程會調用await方法,將自身阻塞,而被等待的線程在完成自身的功能操作後,會調用countDown方法,將共享資源減1,當共享資源爲0時,所有調用await的線程都將被喚醒,繼續後續代碼。
在具體實現上,CountDownLatch使用了AQS的基本框架,下圖以countDown和await兩個主要方法爲例,解析了具體的調用流程。
在實現CountDownLatch時,需要實現的方法爲tryReleaseShared和tryAcquireShared方法。兩個方法分別表示線程完成共享資源訪問的具體操作和判斷等待線程是否可以被喚醒。由於訪問的共享資源被設置爲AQS的state值,所有兩個函數實際上就是對state值的判斷和加減。具體代碼實現如下:
//共享式鎖需要實現的AQS的方法,AQS只提供了throw的默認實現
@Override
protected int tryAcquireShared(int arg) {
return (getState() == 0) ? 1 : -1;
}
//共享式鎖需要實現的AQS的方法,AQS只提供了throw的默認實現
@Override
protected boolean tryReleaseShared(int arg) {
for (; ; ) {
int c = getState();
if (c == 0)
return false;
int nextC = c - 1;
if (compareAndSetState(c, nextC))
return nextC == 0;
}
}
在實現一個CountDownLatch之後,筆者自己寫了個測試用例進行測試,創建5個子線程SubDevice,這些各自子線程會各自睡覺一段時間,表示執行時間,而主線程會等待所有的子線程完成操作後,才輸出相應的結果。
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
CountDownLatchImpl countDownLatch = new CountDownLatchImpl(5);
for (int i = 1; i <= 5; i++) {
new SubDevice((5 - i) * 1000, countDownLatch).start();
}
countDownLatch.await();
System.out.println("main thread:all device load done.");
}
private static class SubDevice extends Thread {
private final long processTime;
private final CountDownLatchImpl countDownLatch;
public SubDevice(long processTime, CountDownLatchImpl countDownLatch) {
this.processTime = processTime;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
Thread.sleep(processTime);
System.out.println(Thread.currentThread() + " load done.");
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
總結:
在解析了CountDownLatch之後,發現該組件在一些某個線程需要等待其他一些線程的處理結果的應用場景中有比較大的作用,但是由於state值在CountDownLatch設置之後無法更改,在一次使用之後無法重置,難以重複利用,這應該也是它最明顯的一個缺點。