一、閉鎖 CountDownLatch
一個同步工具類,允許一個或者多個線程一直等待,直到其他線程的操作都執行完成之后再繼續往下執行。
使用場景:在一些應用場合中,需要等待某個條件達到要求后才能做后面的事情;同時當線程都完成后也會觸發事件,以便進行后面的操作。 這個時候就可以使用CountDownLatch。
CountDownLatch最重要的方法是countDown()和await(),前者主要是計數減一,后者是等待計數到0,如果沒有到達0,就繼續阻塞等待。
如上圖,左邊三只小熊,可以當成三個線程,每一只撞到欄桿,計數器就減1,這相當于執行了countDown方法;
右邊有兩只暴走小熊在等待計數器變為0,可以當成兩個線程,執行了await方法;
最終左邊三只暴走小熊抵達了欄桿處,計數器變為0,喚醒了右邊的暴走小熊,暴走小熊就開始動起來了。
二、執行原理
CountDownLatch是基于AQS共享模式的使用。
如下圖,我們通過給CountDownLatch構造函數傳入state的值。
countDown方法本質是釋放共享鎖,核心實現邏輯是:state>0 && state-1,如果state>0,則state減一,否則執行失敗;
await方法本質是獲取共享鎖,核心實現是:getState()==0,如果state==0,則表示獲取成功,否則線程阻塞進入等待隊列;
當state減到0的時候,會喚醒等待隊列中的所有線程,嘗試繼續獲取共享鎖,這個時候正常是所有線程都能獲取成功的。
三、CountDownLatch的用法
3.1 CountDownLatch典型用法1
某一線程在開始運行前等待n個線程執行完畢。將CountDownLatch的計數器初始化為new CountDownLatch(n),每當一個任務線程執行完畢,就將計數器減1 countdownLatch.countDown(),當計數器的值變為0時,在CountDownLatch上await()的線程就會被喚醒。一個典型應用場景就是啟動一個服務時,主線程需要等待多個組件加載完畢,之后再繼續執行。
/**
* @Description: 工廠中,質檢,5個工人檢查,所有人都認為通過,才通過
*/
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(5);
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
final int no = i+1;
executorService.submit(() -> {
try {
Thread.sleep(new Random().nextInt(10000));
System.out.println("NO." + no + "完成了檢查");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
}
});
}
System.out.println("等待5個人檢查完......");
countDownLatch.await();
System.out.println("所有人都完成了工作,等待進入下一環節");
}
}
3.2 CountDownLatch典型用法2
實現多個線程開始執行任務的最大并行性。注意是并行性,不是并發,強調的是多個線程在某一時刻同時開始執行。類似于賽跑,將多個線程放到起點,等待發令槍響,然后同時開跑。做法是初始化一個共享的CountDownLatch(1),將其計算器初始化為1,多個線程在開始執行任務前首先countdownlatch.await(),當主線程調用countDown()時,計數器變為0,多個線程同時被喚醒。
/**
* @Description: 模擬100米跑步,5名選手都準備好了,只等裁判員一聲令下,所有人同時開跑
*/
public class CountDownLatchDemo2 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1);
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
final int no = i+1;
executorService.submit(() -> {
System.out.println("No." + no + ",準備完畢,等待發令槍");
try {
countDownLatch.await();
System.out.println("No." + no + ",開始跑步");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
//裁判員檢查發令槍......
Thread.sleep(5000);
System.out.println("發令槍響,比賽開始......");
countDownLatch.countDown();
}
}
3.3 CountDownLatch兩種用法結合使用
/**
* @Description: 模擬100米跑步,5名選手都準備好了,只等裁判員一聲令下,所有人同時開跑,
* 所有人都到終點后,比賽結束
*/
public class CountDownLatchDemo3 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch begin = new CountDownLatch(1);
CountDownLatch end = new CountDownLatch(5);
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
final int no = i+1;
executorService.submit(() -> {
System.out.println("No." + no + ",準備完畢,等待發令槍");
try {
begin.await();
System.out.println("No." + no + ",開始跑步");
Thread.sleep(new Random().nextInt(10000));
System.out.println("No." + no + ",跑到終點了");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
end.countDown();
}
});
}
//裁判員檢查發令槍......
Thread.sleep(5000);
System.out.println("發令槍響,比賽開始......");
begin.countDown();
//等待5個線程都執行完畢之后
end.await();
System.out.println("所有人到達終點,比賽結束");
}
}
3.4 CountDownLatch注意點
- 擴展用法:多個線程等待多個線程完成執行后,再同時執行。
- CountDownLatch是不能夠重用的,如果需要重新計數,可以考慮使用CyclicBarrier或者創建新的CountDownLatch實例。
四、源碼
4.1 構造方法:創建一個Sync對象,而Sync繼承AQS
public class CountDownLatch {
private final Sync sync;
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
//設置計數器實際上是將值賦給了AQS狀態變量state
this.sync = new Sync(count);
}
}
4.2 Sync 是CountDownLatch的內部私有類,組合到CountDownLatch里
public class CountDownLatch {
private static final class Sync extends AbstractQueuedSynchronizer {
//設置計數器實際上是將值賦給了AQS狀態變量state
Sync(int count) {
setState(count);
}
//獲取狀態變量state的值
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
//循環進行CAS,直到當前線程完成CAS減去1操作
for (;;) {
int c = getState();
//當前狀態值為0則直接返回
if (c == 0)
return false;
int nextc = c-1;
//使用CAS讓計數器值減去1
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
private final Sync sync;
}
在AQS中state是一個private volatile int類型的變量。CountDownLatch使用state來計數,CountDownLatch的getCount最終調用的是AQS的getState(),返回state進行計數。
4.3 await()方法:調用AQS的acquireSharedInterruptibly方法
public class CountDownLatch {
private final Sync sync;
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
}
- acquireSharedInterruptibly方法:獲取共享鎖
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
//獲取共享鎖
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//判斷線程是否為中斷狀態,如果是拋出interruptedException
if (Thread.interrupted())
throw new InterruptedException();
//嘗試獲取共享鎖,嘗試成功就返回,否則調用doAcquireSharedInterruptibly方法
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
}
- tryAcquireShared方法:嘗試獲取共享鎖
public class CountDownLatch {
private final Sync sync;
private static final class Sync extends AbstractQueuedSynchronizer {
//嘗試獲取共享鎖,重寫AQS里面的方法
protected int tryAcquireShared(int acquires) {
//鎖狀態 == 0,表示所沒有被任何線程所獲取,
//即是可獲取的狀態,否則鎖是不可獲取的狀態
return (getState() == 0) ? 1 : -1;
}
}
}
- doAcquireSharedInterruptibly方法:會使得當前線程一直等待,直到當前線程獲取到鎖(或被中斷)才返回
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//創建“當前線程”的Node節點,且node中記錄的鎖是“共享鎖”類型,并將節點添加到CLH隊列末尾。
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) {
// 獲取成功,把當前節點變為新的head節點,
//并且檢查后續節點是否可以在共享模式下等待,
//并且允許繼續傳播,則調用doReleaseShared繼續喚醒下一個節點嘗試獲取鎖
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
//前繼節點不是頭節點,當前線程一直等待,直到獲取到鎖
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
}
- shouldParkAfterFailedAcquire方法:判斷當前線程獲取鎖失敗之后是否需要掛起
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
/*說明:4.shouldParkAfterFailedAcquire 返回當前線程是否應該阻塞
(01) 關于waitStatus請參考下表(中擴號內為waitStatus的值)
CANCELLED[1] -- 當前線程已被取消
SIGNAL[-1] -- “當前線程的后繼線程需要被unpark(喚醒)”。
一般發生情況是:當前線程的后繼線程處于阻塞狀態,
而當前線程被release或cancel掉,因此需要喚醒當前線程的后繼線程。
CONDITION[-2] -- 當前線程(處在Condition休眠狀態)在等待Condition喚醒
PROPAGATE[-3] -- (共享鎖)其它線程獲取到“共享鎖”
[0] -- 當前線程不屬于上面的任何一種狀態。
(02) shouldParkAfterFailedAcquire()通過以下規則,判斷“當前線程”是否需要被阻塞。
規則1:如果前繼節點狀態為SIGNAL,表明當前節點需要被unpark(喚醒),此時則返回true。
規則2:如果前繼節點狀態為CANCELLED(ws>0),說明前繼節點已經被取消,則通過先前回溯找到一個有效(非CANCELLED狀態)的節點,并返回false。
規則3:如果前繼節點狀態為非SIGNAL、非CANCELLED,則設置前繼的狀態為SIGNAL,并返回false。
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 前驅節點的狀態
int ws = pred.waitStatus;
// 如果前驅節點是SIGNAL狀態,則意味著當前線程需要unpark喚醒,此時返回true
if (ws == Node.SIGNAL)
return true;
// 如果前繼節點是取消的狀態即前驅節點狀態為CANCELLED
if (ws > 0) {
// 從隊尾向前尋找第一個狀態不為CANCELLED的節點
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 將前驅節點的狀態設置為SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
}
4.4 countDown()源碼
public class CountDownLatch {
private final Sync sync;
public void countDown() {
//該方法其實調用AQS中的releaseShared(1)釋放共享鎖方法。
sync.releaseShared(1);
}
}
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
//目的是讓當前線程釋放它所持有的共享鎖,它首先會通過tryReleaseShared()去嘗試釋放共享鎖。
//嘗試成功,則直接返回;嘗試失敗,則通過doReleaseShared()去釋放共享鎖。
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
}
- tryReleaseShared()在CountDownLatch.java中被重寫,釋放共享鎖,將鎖計數器-1
public class CountDownLatch {
private final Sync sync;
private static final class Sync extends AbstractQueuedSynchronizer {
protected boolean tryReleaseShared(int releases) {
for (;;) {
// 獲取“鎖計數器”的狀態
int c = getState();
if (c == 0)
return false;
// “鎖計數器”-1
int nextc = c-1;
// 通過CAS函數進行賦值。
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
}
參考:
https://www.itzhai.com/articles/graphical-several-fun-concurrent-helper-classes.html