CountDownLatch的概念
CountDownLatch的作用很简单,就是一个或者一组线程在开始执行操作之前,必须要等到其他线程执行完才可以。例如等待学生考完收卷的老师。
源码分析
核心成员变量
private final Sync sync;
是一个基于AQS实现的同步器
Sync对AQS的重写
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
其中同步器的state承接CountDownLatch的计数count
tryAcquireShared
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
可见计数count是0,就返回1
tryReleaseShared
if (c == 0)
return false;
int nextc = c - 1;
if (compareAndSetState(c, nextc))
return nextc == 0;
可见是在做倒数操作
构造函数
this.sync = new Sync(count);
构造同步器sync
await()、await(timeout, TimeUnit) - 等待方法
sync.acquireSharedInterruptibly(1);
await方法调用的就是同步器的acquire方法
if (Thread.interrupted() ||
(tryAcquireShared(arg) < 0 &&
acquire(null, arg, true, true, false, 0L) < 0))
回顾下,就是调用tryAcquireShared,上面看到了具体实现,那么小于0的条件是:计数器count != 0,业务含义就是还有任务没有处理完,要等待
这个时候,执行await的线程(一般是主线程)就会进同步队列里面等着去
除非使用带时间的await方法,到期会重新自旋获取锁
countDown() - 计数方法
sync.releaseShared(1);
countdown方法调用的就是释放锁方法,每次使计数减1
使用的时候,默认每个分配任务的子线程持有一把锁,子线程调用countDown后,归还一把,指导计数count变成0,使用await阻塞的线程就可以拿到锁走下去
countDownLatch使用案例
CountDownLatch典型用法:某一线程在开始运行前等待n个线程执行完毕。将CountDownLatch的计数器初始化为new CountDownLatch(n),每当一个任务线程执行完毕,就将计数器减1 countdownLatch.countDown(),当计数器的值变为0时,在CountDownLatch上await()的线程就会被唤醒。
一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。
public class MyTest {
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10, new ThreadFactoryBuilder().setNameFormat("countDownLatchTest-%s").build());
CountDownLatch countDownLatch = new CountDownLatch(5);
int i = 5;
while (i --> 0) {
executorService.submit(new ext(countDownLatch));
}
countDownLatch.await();
System.out.println("all is over");
}
static class ext implements Runnable {
private CountDownLatch countDownLatch;
public ext(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(RandomUtils.getInt(10));
System.out.printf("%s thread is over%n", Thread.currentThread().getName());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
countDownLatch.countDown();
}
}
}
CountDownLatch的问题
CountDownLatch是一次性的 , 计数器的值只能在构造方法中初始化一次 , 之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后 , 它不能再次被使用。因此这个方法比较适合用于程序启动时一次性加载配置。
CountDown的子线程如果抛异常很可能走不到countDown环节,await的线程就有可能永远被阻塞,存在风险。这里注意使用try-finally逻辑让countDown()方法总能被调用。
评论区