JUC 之 CountDownLatch 源码分析
注意:学习本篇内容之前请先阅读【AQS 之 共享锁 源码剖析】一篇,AQS 是基础。
线程门闩,一个线程同步器用于一个或多个线程等待一组线程完成它们的操作。下面我们来看一个示例,代码如下:
public static void main(String[] args) throws Exception {
// 创建等待2个完成动作的门闩对象,也即 doneSignal 对象的 countDown 方法被调用 2 次后唤醒阻塞在该门闩上的线程(调用 await()方法的线程)
CountDownLatch doneSignal = new CountDownLatch(2);
ExecutorService e = Executors.newFixedThreadPool(4);// 创建一个固定线程池
for (int i = 0; i < 2; ++i) {
e.execute(() -> {
ThreadUtil.printMsg("doWork start");
ThreadUtil.sleep(100);
doneSignal.countDown();// 对门闩计数 减 1
ThreadUtil.printMsg("doOther something");
ThreadUtil.sleep(200);
ThreadUtil.printMsg("doWork end");
});
}
doneSignal.await(); // 主线程阻塞等待门闩计数变为 0 被唤醒
ThreadUtil.printMsg("main exit");
e.shutdown();// 关闭线程池,等待线程池中的任务执行结束
}
// 执行结果:====================================================================
// pool-1-thread-1 : doWork start
// pool-1-thread-2 : doWork start
// pool-1-thread-1 : doOther something
// pool-1-thread-2 : doOther something
// main : main exit
// pool-1-thread-2 : doWork end
// pool-1-thread-1 : doWork end
通过上述示例我们发现核心就在 countDown 和 await 两个方法上。相同的方式,我们先来看看 CountDownLatch 的构造器
public CountDownLatch(int count) {// 创建 CountDownLatch 对象时必须传入 门闩计数器
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);// 创建 Sync 同步器
}
Sync 内部类
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count);// 初始化 state 值
}
int getCount() {
return getState();// 获取当前 state 值
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1; // state > 0 线程永久阻塞
}
// 释放资源,对于 CountDownLatch 来说就是对 state 减 1 操作,直到 state = 0,然后唤醒阻塞线程
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;
}
}
}
await 方法
现成阻塞操作,调用 AQS 的 acquireSharedInterruptibly 可响应中断的方式获取锁(此方法已在【AQS 之 共享锁 源码剖析】一篇已做详细讲解)
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
countDown 方法
对 count 减 1,直到 count = 0,调用 AQS releaseShared 方法,最终会调用 Sync 中 tryAcquireShared 方法来判定线程是否阻塞。
public void countDown() {
sync.releaseShared(1);
}
总结:以上就是 CountDownLatch 的实现原理。就是利用 AQS 共享锁机制的同步器 Sync 来实现的。 此处读者需要注意的是 CountDownLatch 一旦创建只可使用一次,不能重复使用。如果想重复使用请使用 CyclicBarrier,该类后续我们也会详解。