辅助类-CountDownLatch
概念
首先,什么是CountDownLatch?
严格来说,CountDownLatch是 JDK 提供的并发流程控制的工具类
Count - Down - Latch
计数,关闭/向下,门阀
简单来说,用于多线程中的计数及控制线程执行顺序
什么意思呢?我们来看个例子
例子
简单使用
假设我们现在有一家餐厅,快要关门了,但是里面还有客人来用餐,那我们不可能直接将客人赶走,必须等客人吃完,餐厅才能关门。同时,客人用完餐后,还需要结账,餐厅也要将收支统计一下,看一下今天经营的营业状况。
我们现在来写个代码实现一下这个场景
public class Restaurant {
public static void main(String[] args) {
for (int i = 0; i < 6; ++i) {
int finalI = i;
new Thread(() -> System.out.println("用户" + finalI + "完成了用餐")).start();
}
System.out.println("餐厅结束营业");
}
}
我们来看一下这段代码的运行结果
用户0完成了用餐
用户1完成了用餐
用户2完成了用餐
用户3完成了用餐
餐厅结束营业
用户5完成了用餐
用户4完成了用餐
进程已结束,退出代码0
可以看到,代码执行结果在用户完成用餐前餐厅就结束营业了
所以,这时就要CountDownLatch登场了,我们来看一下CountDownLatch的主要方法
| 方法 | 描述 |
|---|---|
| public CountDownLatch(int count) | 传入一个参数,该参数 count 是需要倒数的数值 |
| await() | 调用 await() 方法的线程开始等待,直到倒数结束,也就是 count 值为 0 的时候才会继续执行 |
| await(long timeout, TimeUnit unit) | await() 有一个重载的方法,里面会传入超时参数,这个方法的作用和 await() 类似,但是这里可以设置超时时间,如果超时就不再等待了 |
| countDown() | 也就是将 count 值减 1,直到减为 0 时,之前等待的线程会被唤起 |
可以看到,当我们调用await()方法,主线程都将等到CountDownLatch降为0才会继续执行
所以,看一下修改后的代码
public class Restaurant {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 6; ++i) {
int finalI = i;
new Thread(() -> {
System.out.println("用户" + finalI + "完成了用餐");
countDownLatch.countDown();
}).start();
}
countDownLatch.await();
System.out.println("餐厅结束营业");
}
}
看一下执行结果
用户0完成了用餐
用户1完成了用餐
用户2完成了用餐
用户3完成了用餐
用户4完成了用餐
用户5完成了用餐
餐厅结束营业
进程已结束,退出代码0
可以看到结果这样就正常了。
加深一下
接下来我们加一些条件,现在客人离开时会付款,同时餐厅结束营业时要统计营业额。我们来看一下实现
public class RestaurantTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
List<FutureTask<Integer>> futureTaskList = new ArrayList<>();
int i;
int res = 0;
for (i = 0; i < 6; ++i) {
int finalI = i + 1;
futureTaskList.add(new FutureTask<>(
() -> {
System.out.println("用户" + finalI + "完成了用餐,付款:" + finalI + "元");
countDownLatch.countDown();
return finalI;
}
));
new Thread(futureTaskList.get(i)).start();
res += futureTaskList.get(i).get();
}
System.out.println("餐厅结束营业,营业额:" + res);
}
}
这段代码咋一看没有问题,通过FutureTask来创建线程,然后获取线程处理返回的值,但我们要清楚,FutureTask的get()方法是一个阻塞方法,其会阻塞线程的执行,从而降低我们代码的性能。
那么可能有小伙伴就想,我先判断FutureTask是否执行完再统计结果不就行了吗?
那么,我们加上这句
while (isDone(futureTaskList)) {
}
private static boolean isDone(List<FutureTask<Integer>> futureTaskList) {
for (int i = 0; i < 6; ++i) {
if (!futureTaskList.get(i).isDone()) {
return true;
}
}
return false;
}
可以看到,加上这句后我们代码变得是否臃肿,并且我们主线程要不断的调用isDone()方法循环判断,非常浪费性能,这时CountDownLatch就非常好用了
看使用了CountDownLatch的代码
public class RestaurantTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
CountDownLatch countDownLatch = new CountDownLatch(6);
List<FutureTask<Integer>> futureTaskList = new ArrayList<>();
int i;
for (i = 0; i < 6; ++i) {
int finalI = i + 1;
futureTaskList.add(new FutureTask<>(
() -> {
System.out.println("用户" + finalI + "完成了用餐,付款:" + finalI + "元");
countDownLatch.countDown();
return finalI;
}
));
new Thread(futureTaskList.get(i)).start();
}
countDownLatch.await();
int res = 0;
for (i = 0; i < 6; ++i) {
res += futureTaskList.get(i).get();
}
System.out.println("餐厅结束营业,营业额:" + res);
}
}
非常简洁,看一下执行的结果:
用户1完成了用餐,付款:1元
用户2完成了用餐,付款:2元
用户3完成了用餐,付款:3元
用户4完成了用餐,付款:4元
用户5完成了用餐,付款:5元
用户6完成了用餐,付款:6元
餐厅结束营业,营业额:21
进程已结束,退出代码0
非常不错
这就是这篇文章的内容了,欢迎大家的讨论,如有错漏,也请指出,谢谢~