CountDownLatch源码分析
CountDowntLatch的作用是让主线程等待所有的子线程执行完毕之后再进行执行,同时它是基于AQS进行实现的,所以它底层肯定是通过自定义AQS共享模式下的同步器来实现的,该同步器需要重写AQS提供的tryAcquireShared()以及tryReleaseShared()方法,只需要告诉AQS是否尝试获取同步资源以及释放同步资源成功即可。
AQS子类需要定义以及维护同步状态的值,在CountDownLatch中,同步状态state的值为同步资源的个数。
CountDownLatch的结构
public class CountDownLatch {
/**
* 存在一个AQS共享模式下的同步器
*/
private static final class Sync extends AbstractQueuedSynchronizer {
// ......
}
// 存在一个全局的同步器属性
private final Sync sync;
/**
* 构建方法初始化同步器,并指定同步资源的个数
*/
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
/**
* 让主线程进行阻塞
*/
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
/**
* 让倒数器-1
*/
public void countDown() {
sync.releaseShared(1);
}
public long getCount() {
return sync.getCount();
}
public String toString() {
return super.toString() + "[Count = " + sync.getCount() + "]";
}
}
可以看到CountDownLatch中定义了一个同步器,然后存在一个全局的同步器属性,并且通过构建方法来初始化同步器,通过count参数来指定同步资源的个数。
同时CountDownLatch的await()方法将会直接调用AQS的acquireSharedInterruptibly()方法,countDown()方法直接调用AQS的releaseShared()方法。
剖析同步器
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) {
// 只有当同步状态的值为0,方法才返回true
return (getState() == 0) ? 1 : -1;
}
/**
* 尝试释放同步资源
*/
protected boolean tryReleaseShared(int releases) {
// 死循环保证CAS操作成功
for (;;) {
int c = getState();
if (c == 0)
return false;
// 让同步状态的值-1
int nextc = c-1;
// 只有当线程释放同步资源后,同步状态的值0时,该方法才会返回true
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
tryAcquireShared()方法用于尝试获取同步资源,同时AQS规定,如果获取同步资源成功则返回剩余的可用资源个数,否则返回负数,但在CountDownLatch的同步器的tryAcquireShared()方法中,只有当同步状态的值为0时,才表示获取同步资源成功,方法返回1,否则都为获取同步资源失败,方法返回-1。
tryReleaseShared()方法用于尝试释放同步资源,同时AQS规定,如果释放同步资源成功,则返回true,否则返回false,一般情况下方法都会返回true(当前同步资源的值 + 要释放的同步资源个数),但是在CountDownLatch的同步器的tryReleaseShared()方法中,并没有累加同步状态的值,而是当线程每次释放同时资源时,都会将同步状态的值-1,只有当线程释放同步资源后,同步状态的值为0时,该方法才会返回true。
流程总结
1.首先创建一个CountDownLatch实例,并指定倒数器的阈值。
2.主线程调用CountDownLatch的await()方法进行阻塞,该方法会直接调用AQS的acquireSharedInterruptibly()方法,acquireSharedInterruptibly()方法又会调用同步器的tryAcquireShared()方法,尝试获取同步资源,tryAcquireShared()方法只有当同步状态的值为0时,才表示获取同步资源成功,方法返回1,由于目前同步状态的值不为0,因此方法返回-1,因此该线程将会封装成Node节点,然后加入到等待队列当中,该线程将会进行阻塞。
3.子线程调用CountDownLatch的countDown()方法让倒数器-1,该方法会直接调用AQS的releaseShared()方法,releaseShared()方法又会调用同步器的tryReleaseShared()方法,尝试释放同步资源,但是在tryReleaseShared()方法中,每次当线程释放同步资源时,都会将同步状态的值-1,只有当线程释放同步资源后,同步状态的值为0时,该方法才会返回true,否则返回false,如果tryReleaseShared()方法返回false,那么就不做任何处理,只有当该方法返回true时,也就是最后一个子线程执行了countDown()方法,将同步状态的值设置为0,那么就会唤醒离头节点最近的同时等待状态不为CANCELLED的后继节点,也就是主线程,然后主线程调用tryAcquireShared()方法尝试获取同步资源,由于当前同步状态的值已经为0,因此tryAcquireShared()方法返回1,然后主线程直接返回,做自己的事情。
FAQ
CountDownLatch为什么不能重用?
不能重用,因此当主线程被唤醒后,然后调用tryAcquireShared()方法获取了同步资源,然后就直接返回,做自己的事情,永远都不会释放同步资源,因此不能重用。