什麼是CountDownLatch
CountDownLatch稱之爲閉鎖,它可以使一個或一批線程在閉鎖上等待,等到其他線程執行完相應操作後,閉鎖打開,這些等待的線程纔可以繼續執行。確切的說,閉鎖在內部維護了一個倒計數器。通過該計數器的值來決定閉鎖的狀態,從而決定是否允許等待的線程繼續執行。
常用方法
public CountDownLatch(int count):構造方法,count表示計數器的值,不能小於0,否者會報異常。
public void await() throws InterruptedException:調用await()會讓當前線程等待,直到計數器爲0的時候,方法纔會返回,此方法會響應線程中斷操作。
public boolean await(long timeout, TimeUnit unit) throws InterruptedException:限時等待,在超時之前,計數器變爲了0,方法返回true,否者直到超時,返回false,此方法會響應線程中斷操作。
public void countDown():讓計數器減1
CountDownLatch使用步驟:
- 創建CountDownLatch對象
- 調用其實例方法
await()
,讓當前線程等待 - 調用
countDown()
方法,讓計數器減1 - 當計數器變爲0的時候, 喚醒阻塞在CountDownLatch上的線程(用阻塞這個詞不太嚴格,後續有講)
源碼解析:
1.CountDownLatch 初始化過程,初始化內部計數器(aqs 的status變量)
//創建一個CountDownLatch對象,需要等待四個任務執行
CountDownLatch countDownLatch = new CountDownLatch(4);
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
//Sync爲CountDownLatch 的內部類,初始化aqs的status 變量爲 count
//看過筆記上一篇ReentrantLock分析, 對這一幕應該比較數據
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
//判斷計數器(staus)是否爲0
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
//對內部計數器減1,如果減完1等於0 返回true
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;
}
}
}
2. countDownLatch.await();
//調用的父類 aqs中的 acquireSharedInterruptibly方法
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//判斷中斷狀態
if (Thread.interrupted())
throw new InterruptedException();
//tryAcquireShared該方法爲 內部類sync中的方法,具體實現見上文中sync類
//判斷是否所有線程都已經執行完(status==0意味着 當前任務都已經執行完成)
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
//是不是很熟悉?
//如果該當前線程爲首節點就自旋等待所有任務執行完成,否則就掛起當前線程
//如果自旋的過程中發現任務已經執行完成,就喚醒所有等待在等待隊列上的線程(所有調用await的線程)
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//創建一個等待節點(具體實現請見筆者上篇reentrantlock解析)
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
//如果當前結點是等待隊列中的第一個結點,判斷當前任務是否執行完(status == 0),如果沒有在執行的任務,重置head節點,並喚醒所有等待在 等待隊列的線程(所有調用await的線程)
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
//shouldParkAfterFailedAcquire檢測前置節點是否爲等待的節點,是就直接返回true,執行parkAndCheckInterrupt掛起當前線程,
//如果當前結點爲首節點,就把當前結點的等待狀態(waitstatus)設爲-1,並返回false,繼續自旋等待
//具體詳情,見筆者的reentrantLock 解析
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
//如果該方法因爲某些特殊情況意外的退出(沒有獲取鎖就退出了),那麼就取消當前線程的等待
if (failed)
cancelAcquire(node);
}
}
3.countDownLatch.countDown() 實現
public void countDown() {
sync.releaseShared(1);
}
//syn父類aqs的方法
public final boolean releaseShared(int arg) {
//對內部計數器減1,如果等於0返回true
if (tryReleaseShared(arg)) {
//喚醒所有等待在CountDownLatch上的阻塞線程
doReleaseShared();
return true;
}
return false;
}
//喚醒所有等待線程
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
//如果當前等待節點=-1,說明該線程被阻塞,喚醒它
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
//判斷該節點線程是否已經被喚醒
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
//全部喚醒了
if (h == head)
break;
}
}
該篇主要講解了CountDownLatch 實現,對CountDownLatch使用場景有疑問的可以參考https://mp.weixin.qq.com/s/WUwuZjgECBgaWujKV6u4CQ該篇博文
文章部分源碼筆者沒有講的很詳細,沒有講到的地方在筆者的ReentrantLock 源碼解讀中可以找到對應的解釋。
對文中有疑問的地方或者寫的不對的地方歡迎交流