CountDownLatch 的描述是“允许一个或者多个线程等待其他线程完成操作”
下面先看一个简单的例子“出门三部曲”
public static void checkGoOut() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(3);
new Thread(()->{
System.out.println("检查手机");
countDownLatch.countDown();
System.out.println("检查钱包");
countDownLatch.countDown();
System.out.println("检查钥匙");
countDownLatch.countDown();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
countDownLatch.await();
System.out.println("检查完毕,可以出门了");
}
检查手机
检查钱包
检查钥匙
检查完毕,可以出门了
以上是个很典型的例子 “一个线程阻塞等待另一个线程完成之后再继续进行”
CountDownLatch可以认为是一个步骤计数器,当所有当步骤都完成计数为0的时候,被阻塞的线程就会被唤醒
CountDownLatch的类结构,通过内部类Sync继承AQS
countDownLatch的主要方法
//构造方法,设置初始步骤计数,不能重复设置,而且计数只减不增
public CountDownLatch(int count)
//使当前线程等待直到计数到零为止,相应中断
public void await() throws InterruptedException
//计数减1,如果减到0,释放等待的线程
public void countDown()
//带超时机制的await,正常唤醒返回true,超时返回false,相应中断
public boolean await(long timeout, TimeUnit unit)throws InterruptedException
接下来看下源码部分,需要先了解AQS的原理
CountDownLatch构造函数,目的就是设置计数值
//调用构造方法,设定计数值
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
//调用内部类的构造方法
this.sync = new Sync(count);
}
Sync(int count) {
//最终调用AQS中的setState,设置state的值
setState(count);
}
await
//countDownLatch采用的是共享锁,并且响应中断
//所以调用了AQS的acquireSharedInterruptibly
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
//在AQS的该方法中,调用了tryAcquireShared,之前也讲过AQS采用了模版方法
//需要开发者重写tryAcquireShared完成获取同步状态部分
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
//countDownLatch内部类Sync中重写了tryAcquireShared
protected int tryAcquireShared(int acquires) {
//因为countDownLatch的计数是只减不增,到0就不会再变了
//所以这里直接判断是否为0就完事了,连同步都不需要做
//当state为0表示已获取同步状态,不需要进入阻塞队列
return (getState() == 0) ? 1 : -1;
}
countDown
//相对应的需要调用AQS的releaseShared,进行锁释放
public void countDown() {
sync.releaseShared(1);
}
//AQS中的该方法调用了tryReleaseShared
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
//如果成功运行到这里,表示state已经为0,可以释放之前被await阻塞的线程
doReleaseShared();
return true;
}
return false;
}
//主要逻辑是线程安全的将state值减去1,如果为0,表示可以唤醒被await阻塞的线程
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;
}
}
整个逻辑相当的简单,因为线程的入队、阻塞、唤醒、出队这一列过程AQS已经实现了,CountDownLatch只需要判断阻塞和唤醒的条件即可
另外CountDownLatch 和 join的功能差不多,俩者的使用场景也类似
区别在于CountDownLatch注重的是步骤,countDown也并不要求必须在某个线程内,而join注重的是线程的等待,前者比后者在应用上更灵活