1、简介
CountDownLatch是JUC包下的实现类,具体的使用场景见第2节。CountDownLatch的底层是AQS的共享锁的实现,在实际开发中该类有些鸡肋,通常用CompletableFuture这种更强大的异步任务类来实现业务场景,这里主要做了解,实际开发中并不推荐使用CountDownLatch。
1.1 常用api
// 构造方法,入参是一个int类型的值
public CountDownLatch(int count);
// 阻塞当前线程,将当前线程加入到阻塞队列
public void await() throws InterruptedException();
// 重载上一个方法,加入timeout超时时间,在timeout的时间之内阻塞当前线程,时间一过则当前线程可以执行
public boolean await(long timeout, TimeUnit unit);
// 对计数器进行减1操作,当计数器递减至0时,当前线程会去唤醒阻塞队列里的所有线程
public void countDown();
// 返回当前计数器的值
public long getCount();
2、使用场景
这些场景也是我在网上看的,实际开发中我没有用过CountDownLatch,CountDownLatch对应的使用场景,JDK8中的CompletableFuture类的api都可以覆盖,而且功能更加强大,推荐使用CompletableFuture。
2.1 “发令枪”场景
场景描述:让多个线程等待,满足某一条件后让多个线程在同一时刻一并执行。就像田径项目中裁判员的号令枪一样,号令枪一响,所有准备状态的运动员一起冲出去。
demo如下:
public class CdlMainA {
public static void main(String[] args) {
int threadNum = 5;
CountDownLatch countDownLatch = new CountDownLatch(1);
for (int i = 0; i < threadNum; ++i) {
new Thread(() -> {
try {
//准备完毕的运动员都阻塞在这,等待号令
countDownLatch.await();
// 模拟每个运动员在号令枪响后的出发动作
System.out.println(Thread.currentThread().getName() + " 开始执行任务");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
try {
// 模拟主线程要做的业务
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 模拟裁判员的号令枪发射
countDownLatch.countDown();
}
}
2.2 多个异步任务汇总
比如要查询一些指标,将查询后的若干指标拼接成返回体返回。为了提高接口响应速度,将这些查询请求做成异步的,等所有异步查询请求都查询到结果后,再将结果拼接成返回体返回,此时就需要主线程等待所有异步任务返回后再执行的场景。
demo如下:
public class CdlMainB {
public static void main(String[] args) {
CountDownLatch countDownLatch=new CountDownLatch(4);
new Thread(() -> {
try {
// 模拟每个异步任务的执行
Thread.sleep(2000);
// 标记每个异步任务执行结束
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName() + " 异步任务完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
// 模拟每个异步任务的执行
Thread.sleep(3000);
// 标记每个异步任务执行结束
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName() + " 异步任务完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
// 模拟每个异步任务的执行
Thread.sleep(4000);
// 标记每个异步任务执行结束
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName() + " 异步任务完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
// 模拟每个异步任务的执行
Thread.sleep(5000);
// 标记每个异步任务执行结束
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName() + " 异步任务完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
try {
System.out.println(Thread.currentThread().getName() + " 主线程等待异步任务完成...");
// 主线程阻塞
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + " 所有异步任务完成,主线程开始处理业务");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果如下:
异步任务执行期间,主线程处于阻塞状态,等所有异步任务都完成后,主线程才开始处理业务。
3、源码浅析
CountDownLatch的源码相比ReentrantLock简单太多,主要关注一下几个方法和类。
3.1 Sync内部类
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
Sync是一个内部实现类,继承了AQS类(AQS相关参考我这篇文章:https://www.yuque.com/docs/share/da731dfb-7471-43a6-87d4-11d6425d78bd?# 《AQS》)。该类主要提供两个方法:tryAcquireShared方法(加锁)和tryReleaseShared方法(解锁),这两个方法都是对AQS抽象类中同名方法的覆写。
3.2 await方法(加锁)
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
3.3 countDown方法(解锁)
public void countDown() {
sync.releaseShared(1);
}