【源码】JUC —— CountDownLatch 浅析
前言
CountDownLatch ,主要用于如下场景:待线程阻塞到一定数量后,统一唤醒。有点跟 Semaphore 反着来的意思
先养剧,再看剧
JDK 版本
JDK11
Sync
基于 AQS 实现,内部类 Sync 继承 AQS 实现相关方法
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
跟 Semaphore 的构造类似,但此处的 count
表示“倒计时”阈值
关于 Semaphore ,可阅读
tryAcquireShared
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
当 state == 0
时,才表明获取到资源
tryReleaseShared
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
// 若 state 为 0,即被阻塞线程已经被唤醒了
if (c == 0)
return false;
// 否则,CAS 修改 state 递减
int nextc = c - 1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
此处相当于是 倒计时,倒计时到 0 时,所有阻塞的线程被唤醒,同时起跑
得益于 AQS 同步资源获取的实现。不同于 独占锁, AQS 在获取同步资
源后,也会尝试唤醒符合条件的排队节点。因此可以保证,排队节点几乎
同时被唤醒,达到“倒计时”结束同时“起跑”的效果
构造
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
初始化“倒计时”阈值
方法实现(委托)
/**
* 获取 共享资源,响应中断
* 此处的 共享资源 实际是设置的 倒计时
* 因此会在 大于0 是进入队列排队
* 倒计时结束后被唤醒
*/
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();
}
反向实现 共享锁
demo
public class CountDownLatchTest {
static CountDownLatch countDownLatch = new CountDownLatch(4);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
test();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t" + i).start();
TimeUnit.SECONDS.sleep(1);
countDownLatch.countDown();
}
countDownLatch.countDown();
}
static void test() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + " start");
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + " done");
}
}
结果:
--------- 陆续 ------
t0 start
t1 start
t2 start
--------- 同时 -------
t0 done
t2 done
t1 done
三个线程 t0
t1
t2
依次进入并等待,待“倒计时”结束后,同时被唤醒
总结
对于某些需要等待目标线程完成操作后才继续之后动作的场景,可以使用 CountDownLatch,比如 SpringBoot 提供的 BackgroundPreinitializer 事件监听器