我们在创建CountDownLatch时,通常会设置一个同步共享资源值:
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
如果这个同步共享资源值<0,则直接抛出IllegalArgumentException异常。
而这个同步共享资源值用于创建同步控制器。
Sync
在CountDownLatch中,内部同样实现了AbstractQueuedSynchronizer,资源获取与释放的方式是通过共享模式进行的。
我们继续看CountDownLatch#Sync内部类的方法。
构造方法
CountDownLatch内部实现的同步控制器在初始化时需要指定AbstractQueuedSynchronizer的同步资源大小:
Sync(int count) {
setState(count);
}
获取同步资源数量
通过CountDownLatch#Sync#getCount()也可以随时获取这个同步资源数量:
int getCount() {
return getState();
}
tryAcquireShared()
tryAcquireShared()用于尝试获取同步共享资源:
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
如果当前的同步资源数量为0,则线程获取同步资源成功,否则获取失败,
为什么这么设计,请看原理分析部分。
tryReleaseShared()
tryReleaseShared()用于尝试释放同步共享资源:
protected boolean tryReleaseShared(int releases) {
// 释放同步共享资源,如果释放后同步共享资源为0,则需要需要唤醒等待线程
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c - 1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
- 当前线程将会进入自旋状态。
- 获取当前的同步共享资源数量:
- 如果当前的同步共享资源状态为0,证明已经释放所有同步共享资源,无需继续释放同步共享资源,同步队列中的等待线程也由其他线程唤醒,直接返回false。
- 计算释放后的剩余同步共享资源,并通过CAS操作更新最新的同步共享资源状态
- 释放同步共享资源后,需要判断此次释放是否需要唤醒同步队列中的等待线程。
释放同步共享资源时,与传进来的releases数量无关,即每次释放的同步共享资源默认为1。
核心方法
await()
在使用CountDownLatch时,我们使用await()等待同步共享资源状态归0,然后调用await()的线程再继续执行:
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
acquireSharedInterruptibly()调用的是AbstractQueueSychronizer中的方法。
await(long timeout, TimeUnit unit)
我们可以是线程在指定时间内等待:
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
tryAcquireSharedNanos()调用的是AbstractQueueSychronizer中的方法。
countDown()
在构造CountDownLatch时设置的同步共享资源数量,需要调用countDown()方法进行递减:
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
// 尝试唤醒后续等待的线程
signalNext(head);
return true;
}
return false;
}
- 首先尝试调用CountDownLatch#Sync#tryReleaseShared()方法释放资源:
- 释放成功,唤醒后继等待释放的节点,返回true。
- 如果tryReleaseShared()方法返回false,证明当前还有未释放的同步共享资源,同步队列中的线程还需要继续等待。
原理分析
我们在看CountDownLatch原理之前,大都会了解到,使用CountDownLatch时,会有至少一个调用await()的线程等待其他线程countDown()同步共享资源直至全部释放后,继续执行。
那么整个流程是如何依靠AbstractQueuedSynchronizer进行的呢?
首先,我们初始化CountDownLatch时,设置了AbstractQueuedSynchronizer#state为我们设定的值。
接着,调用await()进行等待,await()会调用AbstractQueuedSynchronizer#acquireSharedInterruptibly()方法:
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
// 如果线程被中断
// 并且在尝试获取同步资源之后,入队等待获取不到同步资源,抛出中断异常
// 没有抛出中断异常,证明成功获取了同步资源
if (Thread.interrupted() ||
(tryAcquireShared(arg) < 0 &&
acquire(null, arg, true, true, false, 0L) < 0))
throw new InterruptedException();
}
接着,会调用tryAcquireShared()方法,尝试获取共享资源,tryAcquireShared()即为CountDownLatch#Sync#tryAcquireShared()实现,从上文中的代码分析得知,在没有将同步共享资源countDown()到0时,都会返回-1,紧接着调用await()的线程会进入同步队列进行等待。
可以理解为,在同步队列中等待的线程,均是调用await()的线程。
我们在调用countDown()时,会逐步递减AbstractQueuedSynchronizer#state的值。
当AbstractQueuedSynchronizer#state为0时,会通过AbstractQueuedSynchronizer#releaseShared()中的signalNext()唤醒同步队列中等待的线程,这样调用await()进行等待的线程就被唤醒了。
而事实上,它也没有对AbstractQueuedSynchronizer#state产生实质影响,也就是没有获得任何实质的同步共享资源。