从有点基础开始学JUC:辅助类-CountDownLatch

134 阅读3分钟

辅助类-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来创建线程,然后获取线程处理返回的值,但我们要清楚,FutureTaskget()方法是一个阻塞方法,其会阻塞线程的执行,从而降低我们代码的性能。

那么可能有小伙伴就想,我先判断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

非常不错

这就是这篇文章的内容了,欢迎大家的讨论,如有错漏,也请指出,谢谢~