CountDownLatch源码分析
简介
CountDownLatch提供的是一个计数器倒数的功能。所有调用了await(long timeout, TimeUnit unit)/await()方法的线程都处于阻塞的状态,通过countDown()方法递减计数器,当计数器递减为0或线程中断的时候(await(long timeout, TimeUnit unit)在超时时间达到的情况下也会唤醒线程)唤醒AQS队列中的线程,线程重新恢复运行。
关键方法
public CountDownLatch(int count);//构造方法,创建CountDownLatch对象,指定计数器
public void await();//线程阻塞,等待计数器为0后结束阻塞
public boolean await(long timeout, TimeUnit unit);//线程在一定的时间内阻塞
public void countDown();//计数值减一
public long getCount();//获取计数器当前值
方法实现
1. CountDownLatch(int count)构造器实现
/**
* 根据给定的计数器值初始化CountDownLatch对象,计数器值不能小于0
*/
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
/**
* Sync内部类,继承AbstractQueuedSynchronizer(著名的AQS)
*/
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
//设置计数器值,计数器为volatile修饰
setState(count);
}
//获取计数器当前值
int getCount() {
return getState();
}
//尝试获取共享锁,即判断计数器是否归零,归零则获取锁成功
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
//尝试释放共享锁,计数器值递减,判断计数器是否归零,归零则释放共享锁
protected boolean tryReleaseShared(int releases) {
// for循环+CAS操作,for循环保证操作成功,CAS保证原子性操作
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
2. await()方法实现
await方法主要功能就是阻塞线程,直到计数器归零后,线程恢复运行。
实现逻辑:
- 尝试获取共享锁(即判断计数器值是否为0),获取共享锁成功则直接返回,线程不阻塞
- 在获取共享锁失败的情况下,将当前线程加入到AQS队列中
- 如果当前线程所在节点的前驱为AQS队列的头结点(head),再次尝试获取锁(因为AQS队列是FIFO,前驱为头结点,那么有可能此时计数器已经归零了,所以需要再次尝试),获取锁成功,当前线程所在的节点设置为头结点(head),并向后传播。
- 在尝试获取共享锁不成功的情况下,判断是否需要将线程挂起(首节点为SIGNAL的状态下需要挂起,会剔除队列中CANCLED状态的节点),在需要挂起的情况下,阻塞线程,将其挂起。等待计数器归零,调用LockSupport.unpark(thread);恢复线程的运行。
代码分析:
/**
* 阻塞线程线程,直到计数器归零或线程中断
* 如果计数器的值已经为0,该方法立即返回
*
* /
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
/**
* 已可中断的方式获取共享锁,首先判断线程的中断状态,然后至少调用一次
* tryAcquireShared(获取共享锁成功)。否则线程会被加入到AQS队列中,可能会发生重复的
* 阻塞和运行,调用tryAcquireShared直到获取共享锁成功或线程被中断。
* /
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//判断线程是否中断,中断抛异常
if (Thread.interrupted())
throw new InterruptedException();
//尝试获取共享锁,获取成功,立即返回,获取失败,加入等待队列,挂起线程
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
/**
* 获取共享锁,await方法的关键部分代码
*/
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//将当前线程封装成Node加入AQS队列,节点状态为SHARED状态,返回当前线程的Node节点
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
//循环处理,当中断的线程被唤醒后,会重新执行此循环,并进入if(p==head)的分支,
//在setHeadAndPropagate()方法中会重新设置头结点,并且会唤醒当前节点。实现节
//点唤醒的向后传递
for (;;) {
//获取节点的前驱,前驱一定不为空,因为在入队操作时(enq方法),
//在队列为空的时候,会创建空节点作为头结点,然后在头结点后面增加当前节点
final Node p = node.predecessor();
//前驱为头节点,那么有可能计数器已经归零(释放),当前节点尝试去获取共享锁,
//获取成功,则设置当前节点为头结点,并且
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
//尝试获取共享锁成功后,需要将当前节点设置为 AQS 队列中的第一个节点,
//这是 AQS 的规则,队列的头节点表示正在获取锁的节点 ,并且传播释放下一个节点
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
//shouldParkAfterFailedAcquire方法中只有当前驱节点为SIGNAL状态下才返回true
//如果前驱为CANCLED状态,则递归移除CANCLED状态的节点
//如果前驱为其他状态(CONDITION/PROPAGATE)会将其设置为SIGNAL状态
if (shouldParkAfterFailedAcquire(p, node) &&
//调用LockSupport.park();挂起线程,当unpark被调用的时候,恢复运行,
//for循环重新获取共享锁,设置为头结点并向后传播,直到所有的节点都获得共享锁,继续执行
//在线程恢复运行后判断线程是否中断并判断线程是否中断(中断并复位)
//线程恢复有两种方式:1. 主动唤醒线程unpark(thread); 2. 线程被中断
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
/**
* 将线程加入到AQS队列中
*/
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
//队列中已有节点,则尝试在已有链表的尾节点后面加入当前节点,加入成功,返回
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//
enq(node);
return node;
}
/**
* 节点加入队列
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
//如果队列原始状态为空,则创建一个空的节点作为头节点
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
/**
* 设置队列的头节点,并且AQS队列向后传播,释放队列中处于BLOCKED状态下线程
*
* /
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())
// unpark AQS队列中的后继节点
doReleaseShared();
}
}
3. await(long timeout, TimeUnit unit)方法实现
await(long timeout, TimeUnit unit)方法主要功能是实现线程在一定的时间内处于BLOCKED状态,其中有三种方式可以实现线程继续运行:
- 计数器归零
- 达到指定的阻塞时间
- 线程被中断
实现逻辑:
- 尝试获取共享锁(计数器归零),如果获取成功,则直接返回,不阻塞
- 与await()一样,将当前线程节点加入到AQS队列中,并判断前驱是否为head节点,如果是,则尝试获取共享锁,获取成功,重新设置头结点并向后传递
- 如果不是head节点或尝试获取共享锁不成功,通过1微秒为临界值判断是将线程挂起指定的时间还是通过自循环(主要是因为挂起线程涉及到操作系统层面用户态和内核态的切换,代价较高)。
- 线程恢复运行之后通过for循环可以实现线程重新运行。
/**
* 当前线程等待直到计数器归零或线程中断或超过等待时间
* 如果计数器归零,则返回true
* 如果线程被其他线程中断,则抛出InterruptedException异常
* 如果超过等待时间,则返回false
*/
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
/**
*
* /
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquireShared(arg) >= 0 ||
doAcquireSharedNanos(arg, nanosTimeout);
}
/**
*
* /
private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
//时间小于0,则立即超时
if (nanosTimeout <= 0L)
return false;
//获取超时的dealline
final long deadline = System.nanoTime() + nanosTimeout;
//加入AQS队列
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
//获取前驱节点,如果前驱为head节点,则尝试获取共享锁,获取成功,
//则设置当前节点为头结点,并且传播释放下一个节点
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 true;
}
}
nanosTimeout = deadline - System.nanoTime();
//超时时间到达, 当超时时间到达的时候,并不会向后传播,而是
if (nanosTimeout <= 0L)
return false;
//获取共享锁失败,判断是否需要挂起,只有当现在距离最后期限大于1微秒的时候,
//才会被挂起(挂起时间即为当前时间到dealline的时间间隔)
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
4. countDown()方法实现
countDown的比较简单,主要是将计数器值递减,如果计数器值已归零,那么会唤醒头结点的线程,让线程重新恢复运行。头结点被唤醒后,会调用setHeadAndPropagate向后传递唤醒AQS队列中的其他线程。以此实现处于await状态的线程全部都被唤醒。
/**
* 递减计数器值,当计数器值为0的时候,释放所有处于等待状态的线程
* /
public void countDown() {
sync.releaseShared(1);
}
/**
* 释放共享锁,当计数器为0的时候返回true
* /
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
/**
* 共享模式的释放,发信号给后继节点并确保能向后传播
*/
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
// 如果当前节点是 SIGNAL 意味着,它正在等待一个信号,或者说,它在等待被唤醒,
//因此做两件事,1 是重置 waitStatus 标志位,2 是重置成功后, 唤醒下一个节点
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
// 如果本身头节点的 waitStatus 是出于重置状态(waitStatus==0)的,
//将其设置为“传播”状态。意味着需要将状态向后一个节点传播。
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}