CountDownLatch源码分析
CountDownLatch介绍
countDownLatch允许一个或多个线程等待其他线程完成操作。
countDownLatch是在java1.5被引入,跟它一起被引入的工具类还有CyclicBarrier、Semaphore、concurrentHashMap和BlockingQueue。
存在于java.util.cucurrent包下。
CountDownLatch工作流程
countDownLatch的构造函数接收一个int类型的参数作为计数器,如果你想等待N个点完成,这里就传入N。
当我们调用countDownLatch的countDown方法时,N会减1,countDownLatch的await方法会阻塞当前当前线程,直到N变为零。由于countDown方法可以用在任何地方,所以这里说的N个点,可以是N个线程,也可以是1个线程里的N个执行步骤。用在多个线程时,只需把这个countDownLatch的引用传递到线程里即可。
CountDownLatch源码分析
1、CountDownLatch的构造方法
public CountDownLatch(int count) {
//如果计数器小于0,抛出异常
if (count < 0) throw new IllegalArgumentException("count < 0");
//设置state的值为count,Sync是一个内部类
this.sync = new Sync(count);
}
Sync(int count) {
setState(count);
}
2、CountDownLatch的await()方法
public void await() throws InterruptedException {
//调用了aqs的acquireSharedInterruptibly方法
sync.acquireSharedInterruptibly(1);
}
/**
* 共享式的获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,
* 在同一时刻可以有多个线程获取到同步状态,该方法可以响应中断
*/
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//条件成立:说明当前调用await方法的线程 已经是 中断状态了,直接抛出异常..
if (Thread.interrupted())
throw new InterruptedException();
//条件成立:说明当前AQS.state > 0 ,此时将线程入队,然后等待唤醒..
//条件不成立:AQS.state == 0,此时就不会阻塞线程了..
//对应业务层面 执行任务的线程已经将latch打破了。然后其他再调用latch.await的线程,就不会在这里阻塞了
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
/**
* 如果当前的同步状态为0,则返回1,反之返回-1
*/
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//将调用latch.await()方法的线程 包装成node加入到 AQS的阻塞队列当中。
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
//获取当前线程节点的前驱节点
final Node p = node.predecessor();
//条件成立,说明当前线程对应的节点 为 head.next节点
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
//设置当前节点为 head节点,并且向后传播!(依次唤醒!)
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
//shouldParkAfterFailedAcquire 会给当前线程找一个好爸爸,最终给爸爸节点设置状态为 signal(-1),返回true
//parkAndCheckInterrupt 挂起当前节点对应的线程...
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
/**
* 返回当前节点的前驱节点,如果前驱节点为null,则抛出空指针异常
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
/**
* 设置当前节点为 head节点,并且向后传播!(依次唤醒!)
*/
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
//将当前节点设置为 新的 head节点。
setHead(node);
//调用setHeadAndPropagete 时 propagate == 1 一定成立
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
//获取当前节点的后继节点..
Node s = node.next;
//条件一:s == null 什么时候成立呢? 当前node节点已经是 tail了,条件一会成立。 doReleaseShared() 里面会处理这种情况..
//条件二:前置条件,s != null , 要求s节点的模式必须是 共享模式。 latch.await() -> addWaiter(Node.SHARED)
if (s == null || s.isShared())
//基本上所有情况都会执行到 doReleasseShared() 方法。
doReleaseShared();
}
}
/**
* 都有哪几种路径会调用到doReleaseShared方法呢?
* 1.latch.countDown() -> AQS.state == 0 -> doReleaseShared() 唤醒当前阻塞队列内的 head.next 对应的线程。
* 2.被唤醒的线程 -> doAcquireSharedInterruptibly parkAndCheckInterrupt() 唤醒 -> setHeadAndPropagate() -> doReleaseShared()
*/
private void doReleaseShared() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
for (;;) {
Node h = head;
//条件一:h != null 成立,说明阻塞队列不为空..
//不成立:h == null 什么时候会是这样呢?
//latch创建出来后,没有任何线程调用过 await() 方法之前,有线程调用latch.countDown()操作 且触发了 唤醒阻塞节点的逻辑..
//条件二:h != tail 成立,说明当前阻塞队列内,除了head节点以外 还有其他节点。
//h == tail -> head 和 tail 指向的是同一个node对象。 什么时候会有这种情况呢?
//1. 正常唤醒情况下,依次获取到 共享锁,当前线程执行到这里时 (这个线程就是 tail 节点。)
//2. 第一个调用await()方法的线程 与 调用countDown()且触发唤醒阻塞节点的线程 出现并发了..
// 因为await()线程是第一个调用 latch.await()的线程,此时队列内什么也没有,它需要补充创建一个Head节点,然后再次自旋时入队
// 在await()线程入队完成之前,假设当前队列内 只有 刚刚补充创建的空元素 head 。
// 同期,外部有一个调用countDown()的线程,将state 值从1,修改为0了,那么这个线程需要做 唤醒 阻塞队列内元素的逻辑..
// 注意:调用await()的线程 因为完全入队完成之后,再次回到上层方法 doAcquireSharedInterruptibly 会进入到自旋中,
// 获取当前元素的前驱,判断自己是head.next, 所以接下来该线程又会将自己设置为 head,然后该线程就从await()方法返回了...
if (h != null && h != tail) {
//执行到if里面,说明当前head 一定有 后继节点!
int ws = h.waitStatus;
//当前head状态 为 signal 说明 后继节点并没有被唤醒过呢...
if (ws == Node.SIGNAL) {
//唤醒后继节点前 将head节点的状态改为 0
//这里为什么,使用CAS呢?
//当doReleaseShared方法 存在多个线程 唤醒 head.next 逻辑时,
//CAS 可能会失败...
//案例:
//t3 线程在if(h == head) 返回false时,t3 会继续自旋. 参与到 唤醒下一个head.next的逻辑..
//t3 此时执行到 CAS WaitStatus(h,Node.SIGNAL, 0) 成功.. t4 在t3修改成功之前,也进入到 if (ws == Node.SIGNAL) 里面了,
//但是t4 修改 CAS WaitStatus(h,Node.SIGNAL, 0) 会失败,因为 t3 改过了...
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
}
//条件成立:
//1.说明刚刚唤醒的 后继节点,还没执行到 setHeadAndPropagate方法里面的 设置当前唤醒节点为head的逻辑。
//这个时候,当前线程 直接跳出去...结束了..
//此时用不用担心,唤醒逻辑 在这里断掉呢?、
//不需要担心,因为被唤醒的线程 早晚会执行到doReleaseShared方法。
//2.h == null latch创建出来后,没有任何线程调用过 await() 方法之前,
//有线程调用latch.countDown()操作 且触发了 唤醒阻塞节点的逻辑..
//3.h == tail -> head 和 tail 指向的是同一个node对象
//条件不成立:
//被唤醒的节点 非常积极,直接将自己设置为了新的head,此时 唤醒它的节点(前驱),执行h == head 条件会不成立..
//此时 head节点的前驱,不会跳出 doReleaseShared 方法,会继续唤醒 新head 节点的后继...
if (h == head) // loop if head changed
break;
}
}
/**
* 取消指定node参与竞争。
*/
private void cancelAcquire(Node node) {
// 空判断
if (node == null)
return;
//因为已经取消排队了..所以node内部关联的当前线程,置为Null就好了。。
node.thread = null;
//获取当前取消排队node的前驱。
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
//拿到前驱的后继节点。
//1.当前node
//2.可能是其他 ws > 0 的节点。
Node predNext = pred.next;
//将当前node状态设置为 取消状态 1
node.waitStatus = Node.CANCELLED;
/**
* 当前取消排队的node所在 队列的位置不同,执行的出队策略是不一样的,一共分为三种情况:
* 1.当前node是队尾 tail -> node
* 2.当前node 不是 head.next 节点,也不是 tail
* 3.当前node 是 head.next节点。
*/
// 如果当前节点为队尾节点,cas设置当前节点的前驱节点为队尾节点
if (node == tail && compareAndSetTail(node, pred)) {
//修改pred.next -> null. 完成node出队。
compareAndSetNext(pred, predNext, null);
} else {
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
//第二种情况:当前node 不是 head.next 节点,也不是 tail
//条件一:pred != head 成立, 说明当前node 不是 head.next 节点,也不是 tail
//条件二: ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL)))
//条件2.1:(ws = pred.waitStatus) == Node.SIGNAL
// 成立:说明node的前驱状态是 Signal 状态 不成立:前驱状态可能是0 ,极端情况下:前驱也取消排队了..
//条件2.2:(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))
// 假设前驱状态是 <= 0 则设置前驱状态为 Signal状态..表示要唤醒后继节点。
//if里面做的事情,就是让pred.next -> node.next ,所以需要保证pred节点状态为 Signal状态。
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
//情况2:当前node 不是 head.next 节点,也不是 tail
//出队:pred.next -> node.next 节点后,当node.next节点 被唤醒后
//调用 shouldParkAfterFailedAcquire 会让node.next 节点越过取消状态的节点
//完成真正出队。
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
//当前node 是 head.next节点。 更迷了...
//类似情况2,后继节点唤醒后,会调用 shouldParkAfterFailedAcquire 会让node.next 节点越过取消状态的节点
//队列的第三个节点 会 直接 与 head 建立 双重指向的关系:
//head.next -> 第三个node 中间就是被出队的head.next 第三个node.prev -> head
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
3、CountDownLatch的countDown()方法
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
//条件成立:说明当前调用latch.countDown() 方法线程 正好是 state - 1 == 0 的这个线程,需要做触发唤醒 await状态的线程。
if (tryReleaseShared(arg)) {
//调用countDown() 方法的线程 只有一个线程会进入到这个 if块 里面,去调用 doReleaseShared() 唤醒 阻塞状态的线程的逻辑。
doReleaseShared();
return true;
}
return false;
}
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
/**
* 更新 AQS.state 值,每调用一次,state值减一,当state -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;
//cas成功,说明当前线程执行 tryReleaseShared 方法 c-1之前,没有其它线程 修改过 state。
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}