源码分析
是一个共享锁,是通过一个计数器实现的,只要等计数器的值变成0的时候,表示所有线程已经完成,然后就可以恢复线程继续执行。
也是基于AQS实现的,当主线程调用await的时候,会判断state是否等于0,只有等于0的时候才能获取锁,因为CountDownLatch在初始化的时候,会设置一个计数器,所以此时这个线程会被挂起,使用的也是park方法。
线程调用countDown的时候,会cas的将state-1保存起来。如果state-1之后等于0,则开始在共享模式下释放资源。unparkSuccessor释放节点,最后调用unpark 释放许可
内部类
CountDownLatch类存在一个内部类Sync,继承自AbstractQueuedSynchronizer。
private static final class Sync extends AbstractQueuedSynchronizer {
// 版本号
private static final long serialVersionUID = 4982264981922014374L;
// 构造器
Sync(int count) {
setState(count);
}
// 返回当前计数
int getCount() {
return getState();
}
// 试图在共享模式下获取对象状态
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
// 试图设置状态来反映共享模式下的一个释放
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;
}
}
}
对CountDownLatch方法的调用会转发到对Sync或AQS的方法的调用,AQS对CountDownLatch提供支持。
类的属性
public class CountDownLatch {
// 同步队列
private final Sync sync;
//该构造函数可以构造一个用给定计数初始化的CountDownLatch,并且构造函数内完成了sync的初始化,并设置了状态数。
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
// 初始化状态数
this.sync = new Sync(count);
}
}
核心方法
await
此方法将会使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。
public void await() throws InterruptedException {
// 转发到sync对象上
sync.acquireSharedInterruptibly(1);
}
//AQS提供的方法
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//调用了CountDownLatch的内部类Sync的tryAcquireShared
if (tryAcquireShared(arg) < 0)
//AQS类的方法
doAcquireSharedInterruptibly(arg);
}
//CountDownLatch的内部类Sync的tryAcquireShared
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 (;;) { // 无限循环
// 获取node的前驱节点
final Node p = node.predecessor();
if (p == head) { // 前驱节点为头节点
// 试图在共享模式下获取对象状态 CountDownLatch的内部类Sync的tryAcquireShared
int r = tryAcquireShared(arg);
if (r >= 0) { // 获取成功
// 设置头节点并进行繁殖 AQS类的方法
setHeadAndPropagate(node, r);
// 设置节点next域
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) // 在获取失败后是否需要禁止线程并且进行中断检查
// 抛出异常
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//该方法设置头节点并且释放头节点后面的满足条件的结点
private void setHeadAndPropagate(Node node, int propagate) {
// 获取头节点
Node h = head; // Record old head for check below
// 设置头节点
setHead(node);
// 进行判断
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
// 获取节点的后继
Node s = node.next;
if (s == null || s.isShared()) // 后继为空或者为共享模式
// 以共享模式进行释放(可能会调用到) AQS类的方法
doReleaseShared();
}
}
//该方法在共享模式下释放 AQS类的方法
private void doReleaseShared() {
// 无限循环
for (;;) {
// 保存头节点
Node h = head;
if (h != null && h != tail) { // 头节点不为空并且头节点不为尾结点
// 获取头节点的等待状态
int ws = h.waitStatus;
if (ws == Node.SIGNAL) { // 状态为SIGNAL
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) // 不成功就继续
continue; // loop to recheck cases
// 释放后继结点
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) // 状态为0并且不成功,继续
continue;
}
if (h == head) // 若头节点改变,继续循环
break;
}
}
CountDownLatch的await调用大致会有如下的调用链(主要方法调用链)。
countDown
将递减锁存器的计数,如果计数到达零,则释放所有等待的线程
public void countDown() {
//调用AQS类的方法
sync.releaseShared(1);
}
//会以共享模式释放对象
public final boolean releaseShared(int arg) {
//CountDownLatch的tryReleaseShared
if (tryReleaseShared(arg)) {
//AQS的doReleaseShared
doReleaseShared();
return true;
}
return false;
}
//CountDownLatch的tryReleaseShared 会试图设置状态来反映共享模式下的一个释放
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;
}
}
//AQS的doReleaseShared 在共享模式下释放资源
private void doReleaseShared() {
// 无限循环
for (;;) {
// 保存头节点
Node h = head;
if (h != null && h != tail) { // 头节点不为空并且头节点不为尾结点
// 获取头节点的等待状态
int ws = h.waitStatus;
if (ws == Node.SIGNAL) { // 状态为SIGNAL
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) // 不成功就继续
continue; // loop to recheck cases
// 释放后继结点
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) // 状态为0并且不成功,继续
continue; // loop on failed CAS
}
if (h == head) // 若头节点改变,继续循环
break;
}
}
CountDownLatch的countDown调用大致会有如下的调用链。
示例分析
import java.util.concurrent.CountDownLatch;
class MyThread extends Thread {
private CountDownLatch countDownLatch;
public MyThread(String name, CountDownLatch countDownLatch) {
super(name);
this.countDownLatch = countDownLatch;
}
public void run() {
System.out.println(Thread.currentThread().getName() + " doing something");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " finish");
countDownLatch.countDown();
}
}
public class CountDownLatchDemo {
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(2);
MyThread t1 = new MyThread("t1", countDownLatch);
MyThread t2 = new MyThread("t2", countDownLatch);
t1.start();
t2.start();
System.out.println("Waiting for t1 thread and t2 thread to finish");
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " continue");
}
}
//console
Waiting for t1 thread and t2 thread to finish
t1 doing something
t2 doing something
t1 finish
t2 finish
main continue
本程序首先计数器初始化为2。根据结果,存在的方法调用时序图。
首先main线程会调用await操作,此时main线程会被阻塞,等待被唤醒,之后t1线程执行了countDown操作,最后,t2线程执行了countDown操作,此时main线程就被唤醒了,可以继续运行。
main线程执行countDownLatch.await操作,主要调用的函数如下
在最后,main线程就被park了,即禁止运行了。此时Sync queue(同步队列)中有两个节点,AQS的state为2,包含main线程的结点的nextWaiter指向SHARED结点。
t1线程执行countDownLatch.countDown操作,主要调用的函数如下。
此时,Sync queue队列里的结点个数未发生变化,但是此时,AQS的state已经变为1了。
t2线程执行countDownLatch.countDown操作,主要调用的函数如下。
经过调用后,AQS的state为0,并且此时,main线程会被unpark,可以继续运行。当main线程获取cpu资源后,继续运行。
main线程获取cpu资源,继续运行,由于main线程是在parkAndCheckInterrupt函数中被禁止的,所以此时,继续在parkAndCheckInterrupt函数运行。
main线程恢复,继续在parkAndCheckInterrupt函数中运行,之后又会回到最终达到的状态为AQS的state为0,并且head与tail指向同一个结点,该节点的额nextWaiter域还是指向SHARED结点。