什么是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 源码解读中可以找到对应的解释。
对文中有疑问的地方或者写的不对的地方欢迎交流