概述
前段時間在解決請求風控服務器超時的問題時,涉及到到一個CountDownLunch的并發工具類,非常實用,順記自然就去研究了一下相關的并發工具類。
在JDK的并發包里(java.util.concurrent)提供了這樣幾個非常有用的并發工具類來解決并發編程的流程控制。分別是CountDownLatch、CyclicBarrier和Semaphore。
1. CountDownLatch
1.1 CountDownLatch是什么?
CountDownLatch打多是被用在等待多線程完成,具體來說就是允許一個或多個線程等待其他線程完成操作。
1.2 CountDownLatch原理?
API 文檔有這樣一盒解釋
A CountDownLatch is a versatile synchronization tool and can be used for a number of purposes. A CountDownLatch initialized with a count of one serves as a simple on/off latch, or gate: all threads invoking await wait at the gate until it is opened by a thread invoking countDown(). A CountDownLatch initialized to N can be used to make one thread wait until N threads have completed some action, or some action has been completed N times.
構造函數
//Constructs a CountDownLatch initialized with the given count.
public void CountDownLatch(int count) {...}
在 CountDownLunch啟動的時候。主線程必須在啟動其他線程后立即調用CountDownLatch.await()方法。這樣主線程的操作就會在這個方法上阻塞,直到其他線程完成各自的任務。
public void countDown()
在每次任務執行完直接調用,計數器就會減一操作。
public boolean await(long timeout,TimeUnit unit) throws InterruptedException
public void await() throws InterruptedException
這個方法就是用來堵塞主線程的,前者是有等待時間的,可以自定義,后者是無限等待,知道其他count 計數器為0為止。
詳細的 demo 就不在這里粘貼了
如有需要傳送門
1.3 使用場景
超時機制
主線程里面設置好等待時間,如果發現在規定時間內還是沒有返回結果,那就喚醒主線程,拋棄。
開始執行前等待n個線程完成各自任務
例如應用程序啟動類要確保在處理用戶請求前,所有N個外部系統已經啟動和運行了。
死鎖檢測
一個非常方便的使用場景是,你可以使用n個線程訪問共享資源,在每次測試階段的線程數目是不同的,并嘗試產生死鎖。
若有不正之處請多多諒解,并歡迎各位大牛批評指正。
1.4 深入源碼
這里面我簡單的研究了一下CountDownLunch 源碼。
底層是由AbstractQueuedSynchronizer提供支持(后面就簡稱 AQS),所以其數據結構就是AQS的數據結構,而AQS的核心就是兩個虛擬隊列:同步隊列syncQueue 和條件隊列conditionQueue(前者數據結構是雙向鏈表,后者是單向鏈表)不同的條件會有不同的條件隊列。
本省CountDownLunch繼承的是 Object,比較簡單,但是存在內部類,Sync,繼承自AbstractQueuedSynchronizer,我簡單理解一下
private static final class Sync extends AbstractQueuedSynchronizer {
// 版本號
private static final long serialVersionUID = 4982264981922014374L;
// 構造器
Sync(int count) {
setState(count);
}
// 返回當前計數
int getCount() {
return getState();
}
// 試圖在共享模式下獲取對象狀態
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
// 試圖設置狀態來反映共享模式下的一個釋放
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
// 無限循環
for (;;) {
// 獲取狀態
int c = getState();
if (c == 0) // 沒有被線程占有
return false;
// 下一個狀態
int nextc = c-1;
if (compareAndSetState(c, nextc)) // 比較并且設置成功
return nextc == 0;
}
}
}
1.4.1核心函數分析
- await函數
此函數將會使當前線程在鎖存器倒計數至零之前一直等待,除非線程被中斷。其源碼如下
public void await() throws InterruptedException{
// 轉發到sync對象上
sync.acquireSharedInterruptibly(1);
}
源碼可知,對CountDownLatch對象的await的調用會轉發為對Sync的acquireSharedInterruptibly(從AQS繼承的方法)方法的調用。
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
這里先檢測了線程中斷狀態,中斷了則拋出異常,接下來調用tryAcquireShared,tryAcquireShared是Syn的實現的
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
其實就是簡單的獲取了同步器的state,判斷是否為0.
接下來是
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
關鍵點 我看到的是
parkAndCheckInterrupt()
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
執行到此處時,線程會阻塞,知道有其他線程喚醒此線程,執行await之后,上文中的主線程阻塞在這。
- countDown函數
此函數將遞減鎖存器的計數,如果計數到達零,則釋放所有等待的線程
void countDown() {
sync.releaseShared(1);
}
可以看出 對countDown的調用轉換為對Sync對象的releaseShared(從AQS繼承而來)方法的調用。
這里面的具體原理能力有限,有點看不懂,CAS相關的東西。
1.5 小結
不得不說countdownlatch是一個很高的線程控制工具,極大的方便了我們開發。由于知識能力有限,上面是自己的一點見識,有什么錯誤還望提出,便于我及時改進。