ReentrantLock和ReentrantReadWriteLock都實現interface接口,內部又繼承AbstractQueuedSynchronizer。而CountDownLatch是JUC中提供的一個多線程間協同的工具類,並沒有實現interface接口,只在內部繼承實現AQS。
CountDownLatch主要處理:主線程將某個大任務切分成數個小任務,由多個線程處理小任務,而主線程阻塞直到所有任務完成後,主線程再處理接下來的任務。即:處理一個線程與多個線程間的協同問題。
CountDownLatch內部維持的int state屬性值,作爲一個計數器,當計數器的值爲0的時,說明所有任務都執行完畢,主任務可以繼續向下執行。state可以理解爲小任務的個數,甚至可以理解爲線程的個數。
先來一段demo,瞅一下CountDwonLatch的用法:
public class TestCountDownLatch {
private static final int DEFAULT = 3;
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 6; i++) {
Thread thread = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(DEFAULT);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "count down. ");
//CountDownLatch內部維持的計數器-1
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName() + " " + countDownLatch.getCount());
}
});
threads.add(thread);
}
for (Thread thread : threads) {
thread.start();
}
//主線程阻塞,當內部計數器==0時,main線程將被喚醒
System.out.println("666");
countDownLatch.await();
System.out.println("999");
//state計數器不會恢復成初始參數,此時不再阻塞線程
countDownLath.await();
System.out.println("000);
}
}
構造函數,count爲初始化內部計數器的值:
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
CountDownLatch對外提供的使用方法:
//支持超時時間的可中斷方法,阻塞主線程
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
//子任務線程中計數器-1
public void countDown() {
sync.releaseShared(1);
}
//獲取當前計數器的值
public long getCount() {
return sync.getCount();
}
await阻塞主線程的源碼:
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
//AQS提供的獲取共享鎖框架
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
//支持線程中斷時,響應異常
if (Thread.interrupted())
throw new InterruptedException();
//state!=0時,爲負數;則需要阻塞線程
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
//CountDownLatch中實現的判斷條件
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
//AQS提供的熟悉的鎖框架
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;
}
}
//判斷是否可以安全的阻塞,且park後檢測中斷狀態
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
//當被中斷後,需要將node節點置爲null,並從雙向鏈表中刪除。如果前繼是head,還需要喚醒後繼節點,讓其嘗試獲取鎖
if (failed)
cancelAcquire(node);
}
}
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
//根據state是否爲0,來決定是否喚醒鏈表中的阻塞線程
if (tryReleaseShared(arg)) {
//
doReleaseShared();
return true;
}
return false;
}
//state==0時,直接返回false
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
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
//喚醒head後繼節點
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
CountDownLatch基本就結束了,相對於互斥鎖、讀寫鎖,CountDownLatch就是基於AQS的一個多線程間協同工具。
CountDownLatch的特點:
1、應用於多線程協同場景中,且是單線程等待多線程任務結束
2、CountDownLatch中的state值減爲0後,不會重新恢復成初始參數,因此,countDownLatch.await()被喚醒後,再次調用await()起不到阻塞線程的作用