鎖和信號量是控制併發,但是CountDownLatch(倒數計數器)是提供併發(讓線程一起等待到某個條件一起觸發執行),個人感覺CountDownLatch在平常工作環境並不常用,最常見的場景就是開啓多個線程同時執行某個任務,等所有任務執行完成後再做結果彙總;在countdownLatch出現之前都是使用join()方法來實現,但是join不夠靈活,不能夠滿足不同場景下的需求·
1.1 Sync內部類源碼分析
//Sync繼承 AQS使用起state屬性來作爲count
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
//構造器
Sync(int var1) {
//設置state的值爲count
this.setState(var1);
}
//獲取count的值,及state的值
int getCount() {
return this.getState();
}
//重寫父類tryAcquireShared的方法
protected int tryAcquireShared(int var1) {、
//如果state值爲0;返回1否則返回-1
return this.getState() == 0 ? 1 : -1;
}
//重寫 父類tryReleaseShared方法
protected boolean tryReleaseShared(int var1) {
for (;;) {
//獲取state的值
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
//cas更新成功
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
1.2CountDownLatch 構造函數
public CountDownLatch(int count) {
//構造函數傳入的值必須大於0;否則拋出異常
if (count < 0) throw new IllegalArgumentException("count < 0");
//創建一個Sync對象
this.sync = new Sync(count);
}
1.3await() 方法解析
當前線程調用了await()方法後會,會將當前線程阻塞直到出現下面兩種情況之一纔會返回:
當所有線程調用都調用了countDown方法後,也就是說調用了await方法的都要在調用countDown方法一遍使計數器的值爲0
其他線程調用了當前線程的interrupt方法中斷了當前線程, 當前線程會拋出interruptedException異常返回
public void await() throws InterruptedException {
//sync調用父類的acquireSharedInterruptibly方法
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//如果線程被中斷,則拋出異常
if (Thread.interrupted())
throw new InterruptedException();
//如果tryAcquireShared小於0,則進入AQS同步隊列
if (tryAcquireShared(arg) < 0)
//調用AQS的方法進入同步隊列
doAcquireSharedInterruptibly(arg);
}
Sync類的tryAcquireShared方法在state等於0時返回1;否則返回-1
回到AQS的AcquireSharedInterruptibly方法,當Sync類的tryAcquireShared返回1則回到AQS的AcquireSharedInterruptibly方法返回,即await方法返回;
1.4 await(long timeout,TimeUnit unit)源碼分析
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
AQS中的tryAcquireSharedNanos方法
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquireShared(arg) >= 0 ||
doAcquireSharedNanos(arg, nanosTimeout);
}
AQS中的doAcquireSharedNanos方法
private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
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 true;
}
}
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
當線程調用了countDownLacth方法後,當前線程會被阻塞,直到下面的情況之一發生纔會返回
當前線程都調用了countDownLacth對象的countDown方法,也就是計數器爲0的時候,這個時候返回true
設置timeout時間到了,因爲超時兒返回false
其他線程調用了當前線程的interrupt方法中斷了當前線程,當前線程會拋出InterruptException異常後返回
2. countDown()方法
當前線程調用了該方法後悔遞減計數器的值,遞減後如果計數器爲0,則會喚醒await方法而被阻塞的線程,否則什麼都不做
public void countDown() {
sync.releaseShared(1);
}
AQS中的releaseShared方法
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
tryReleaseShared方法需要子類實現;具體實現在CountDownLatch內部類Sync中的tryReleaseShared方法
AQS中的doReleaseShared 方法:
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
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) // loop if head changed
break;
}
}