CountDownLatch简介
public class CountDownLatchDemo {
public static void main(String[] args) {
//开始信号
CountDownLatch startSignal = new CountDownLatch(1);
//完成信号
CountDownLatch doneSignal = new CountDownLatch(10);
for(int i = 0; i < 10; i++){
new Thread(new Worker(startSignal, doneSignal)).start();
}
startSignal.countDown();
System.out.println("让线程开始工作");
try {
doneSignal.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("10个线程已经结束工作");
}
}
class Worker implements Runnable{
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal ;
public Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
this.startSignal = startSignal;
this.doneSignal = doneSignal;
}
@Override
public void run() {
try {
startSignal.await();
System.out.println(Thread.currentThread().getName() + "工作");
doneSignal.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
-
CountDownLatch通过sync继承AQS来使用AQS的共享模式,在初始化时会设置state值
-
线程执行await()时,如果此时state值大于0,线程会把自己包装成Node结点进入阻塞队列,等于0视为通过
-
每次countDown()操作使state值减一,state值为0时,会唤醒阻塞队列中的Node结点
解析await()操作流程
- 在await()操作执行时,如果state值已经被减到0,直接通过,否则进入阻塞队列挂起
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
//AQS的acquireSharedInterruptibly():判断当前stste值,大于0入队,小于0通过
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//判断中断标志位,如果为true直接抛出异常
if (Thread.interrupted())
throw new InterruptedException();
//tryAcquireShared()返回-1:线程将入队挂起,然后等待唤醒
//tryAcquireShared()返回1:直接通过,此时Latch已经被打破,await()的线程将不会再阻塞
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
//CountDownLatch中的sync重写的tryAcquireShared():当前state值为0返回1,否则返回-1
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
//AQS的doAcquireSharedInterruptibly():将线程入队,入队前和唤醒后都会尝试通过
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
//把当前线程包装成Node结点加入到AQS的阻塞队列中,具体在AQS源码阅读(一)中
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;
}
}
//shouldParkAfterFailedAcquire():判断当前线程是否需要被挂起,在这个方法中会保证前置结点状态为SIGNAL
//parkAndCheckInterrupt():挂起当前线程,线程被唤醒后返回当前线程的中断标记
//具体在AQS源码阅读(一)中
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
//如果线程被中断唤醒,抛出异常
throw new InterruptedException();
}
} finally {
//上面没有正常退出,而是异常
if (failed)
//取消当前结点,具体在AQS源码阅读(一)中
cancelAcquire(node);
}
}
//AQS的setHeadAndPropagate():将当前结点设置为头结点,唤醒后面的结点
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
//将当前结点设置为头结点
setHead(node);
//propagate > 0 一定成立,h状态可能为传播状态
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
//当前结点的后继结点
Node s = node.next;
//s == null:当前结点是尾结点
//s.isShared():当前结点的模式是共享模式 addWaiter(Node.SHARED)
if (s == null || s.isShared())
//doReleaseShared():唤醒阻塞队列中的结点,下面具体会写
doReleaseShared();
}
}
解析countDown()操作流程
- countDown()操作会使state值减一,把state值减到0的线程会去唤醒阻塞队列中的结点
public void countDown() {
sync.releaseShared(1);
}
//AQS的releaseShared():对state值减一,如果是把state值减到0的线程,去唤醒阻塞队列中的结点
public final boolean releaseShared(int arg) {
//当前调用countDown() 方法线程正好是把state值减到0的线程
if (tryReleaseShared(arg)) {
//去唤醒阻塞队列中的结点,下面具体会写
doReleaseShared();
return true;
}
return false;
}
//CountDownLatch中的sync重写的tryAcquireShared():将state值减一,减到0的线程返回true
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
//当前的state值
int c = getState();
//state已经被减到0
//已经有线程去唤醒阻塞队列结点了,返回false
if (c == 0)
return false;
int nextc = c-1;
//CAS操作更改state
if (compareAndSetState(c, nextc))
//nextc == 0true,说明当前线程就是去触发唤醒操作的线程
return nextc == 0;
}
}
//AQS的doReleaseShared():唤醒阻塞队列中的结点
private void doReleaseShared() {
for (;;) {
//头结点
Node h = head;
//h != null:阻塞队列不为空
//h == null 什么时候会是这样呢?Latch创建出来后,在线程进行await()操作之前,有线程调用countDown()操作触发了唤醒操作
//h != tail:这不是最后一个结点
//h == tail 什么时候会是这样呢?
//情况一:正常情况下,阻塞队列中的结点依次被唤醒,直到尾结点
//情况二:第一个进行await()操作的线程入队时需要创建一个头结点节点,然后再次自旋入队
// 在await()线程入队完成之前,队列中只有一个头结点
// 此时调用countDown()操作的线程,将state值修改为0,去唤醒阻塞队列中的结点,就会出现这种情况
if (h != null && h != tail) {
//头结点的状态
int ws = h.waitStatus;
//唤醒后继结点
if (ws == Node.SIGNAL) {
//唤醒后继节点前头结点的状态改为0
//为什么使用CAS呢?
//doReleaseShared()存在多个线程唤醒:将state值修改为0的线程,被唤醒的结点成为头结点的线程
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
}
//h == head:
//情况一:刚刚唤醒的后继结点,还未执行到 setHeadAndPropagate()
//情况二:h == null,Latch创建出来后,没有线程执行过await(),阻塞队列为空
//情况三:这是队列中最后一个节点了
//h != head:
//被唤醒的结点迅速执行setHeadAndPropagate(),将自己设置为新的头结点
if (h == head) // loop if head changed
break;
}
}