CountDownLatch详解以及项目实战

1.背景

  • CountDownLatch是在java1.5被引入,跟它一起被引入的工具类还有CyclicBarrier、Semaphore、concurrentHashMap和BlockingQueue。
  • 存在于java.util.cucurrent包下。

2.概念

  • CountDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。
  • 是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有的线程都执行完毕,然后在闭锁上等待的线程就可以恢复执行接下来的任务。

3.源码

  • CountDownLatch类中只提供了一个构造器

    构造一个CountDownLatch,用给定计数值初始化

    参数 count为在线程可以通过await方法之前必须调用countDown的次数

/**
 * Constructs a {@code CountDownLatch} initialized with the given count.
 *
 * @param count the number of times {@link #countDown} must be invoked
 *        before threads can pass through {@link #await}
 * @throws IllegalArgumentException if {@code count} is negative
 */
public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}
  • 类中有三个方法最重要
//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public void await() throws InterruptedException { };   
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };  
//将count值减1
public void countDown() { };  

4.示例

4.1.示例一
4.1.1.示例代码
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @ClassName:CountDownLatchTest2
 * @Description TODO
 * @Create By login name:zkrsun
 * @Date 2021/9/17 14:26
 */
public class CountDownLatchTest2 {
    public static class Work implements Runnable {

        private final int num;
        private final CountDownLatch latch;

        public Work(int num, CountDownLatch latch) {
            this.num = num;
            this.latch = latch;
        }

        public void run() {
            System.out.println("子线程"+Thread.currentThread().getName()+"开始执行");
            doWork();
            latch.countDown();
            System.out.println("子线程"+Thread.currentThread().getName()+"结束执行");
        }

        void doWork() {
            System.out.println("===num ="+num+"====count ="+latch.getCount());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        int num = 10;
        CountDownLatch latch = new CountDownLatch(num);
        for (int i = 0; i < num; i++) {
            executorService.execute(new Work(i,latch));
        }
        System.out.println("主线程"+Thread.currentThread().getName()+"等待子线程执行完成...");
        latch.await();
        System.out.println("主线程"+Thread.currentThread().getName()+"开始执行...");
        if(!executorService.isShutdown()){
            executorService.shutdown();
        }
    }
}
4.1.2.示例结果
子线程pool-1-thread-1开始执行
主线程main等待子线程执行完成...
===num =0====count =10
子线程pool-1-thread-1结束执行
子线程pool-1-thread-1开始执行
===num =1====count =9
子线程pool-1-thread-1结束执行
子线程pool-1-thread-1开始执行
===num =2====count =8
子线程pool-1-thread-1结束执行
子线程pool-1-thread-1开始执行
===num =3====count =7
子线程pool-1-thread-1结束执行
子线程pool-1-thread-1开始执行
===num =4====count =6
子线程pool-1-thread-1结束执行
子线程pool-1-thread-1开始执行
===num =5====count =5
子线程pool-1-thread-1结束执行
子线程pool-1-thread-1开始执行
===num =6====count =4
子线程pool-1-thread-1结束执行
子线程pool-1-thread-1开始执行
===num =7====count =3
子线程pool-1-thread-1结束执行
子线程pool-1-thread-1开始执行
===num =8====count =2
子线程pool-1-thread-1结束执行
子线程pool-1-thread-1开始执行
===num =9====count =1
子线程pool-1-thread-1结束执行
主线程main开始执行...
4.1.3.代码解析

1.创建一个work类实现Runnable接口,实现run方法

2.创建一个线程数为一的线程池

3.每个线程按顺序执行,每个线程执行完毕计数器的计数值减一

4.此时主线程执行await()方法后的代码

4.2.示例二
4.2.1.示例代码
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @ClassName:CountDownLatchTest1
 * @Description TODO
 * @Create By login name:zkrsun
 * @Date 2021/9/17 14:22
 */
public class CountDownLatchTest1 {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(3);
        final CountDownLatch latch = new CountDownLatch(3);
        for (int i = 0; i < 3; i++) {
            Runnable runnable = new Runnable(){

                public void run() {
                    try {
                        System.out.println("子线程"+Thread.currentThread().getName()+"开始执行");
                        Thread.sleep((long) (Math.random() * 10000));
                        System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
                        latch.countDown();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            service.execute(runnable);
        }
        try {
            System.out.println("主线程"+Thread.currentThread().getName()+"等待子线程执行完成...");
            System.out.println("latch.await()after:"+latch.getCount());
            latch.await();
            System.out.println("latch.await()before:"+latch.getCount());
            System.out.println("主线程"+Thread.currentThread().getName()+"开始执行...");

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        service.shutdown();
    }
}
4.2.2.示例结果
子线程pool-1-thread-3开始执行
主线程main等待子线程执行完成...
子线程pool-1-thread-1开始执行
子线程pool-1-thread-2开始执行
latch.await()after:3
子线程pool-1-thread-3执行完毕
子线程pool-1-thread-1执行完毕
子线程pool-1-thread-2执行完毕
latch.await()before:0
主线程main开始执行...
4.2.3.代码解析

1.创建了一个线程数为三的线程池

2.构造了一个计数值为3的计数器

3.三个线程同时执行,打印线程名并给予线程随机休眠时间,每个线程执行完毕后,调用countDown()方法将计数值减1

4.三个线程执行的同时主线程也同时执行直到遇到await()方法停止执行,等待计数器归零

5.当三个线程全部执行完毕后计数器的计数值归为0此时主线程开始执行后续代码

5.项目实战

5.1.项目需求

计算批次指数,存在对于环比的计算比如对于销售金额的月环比,需要获取上个月的数据跟本月的数据进行对比。

5.2.使用CountDownLatch的原因

一开始设计为串行获取,但是遇到bi获取数据结果过慢的问题改为多线程获取数据

多线程获取数据存在一个问题:有的线程执行快,有的线程执行慢导致最终的结果报错

此时引用CountDownLatch,等待本月数据以及上月数据获取完毕后主线程再计算最终结果

5.2.项目代码

只提供Controller代码后续底层代码过多不一一展示

    @GetMapping("/getBatchIndexData")
    public Object getBatchIndexData(@RequestParam(name = "startTime", required = false) String startTime,
                                    @RequestParam(name = "endTime", required = false) String endTime,
                                    @RequestParam(name = "phone", required = false) String phone,
                                    @RequestParam(name = "dayType", required = false) Integer dayType) {
        if (startTime == null) {
            startTime = DateUtil.getDateByNumber(-1, "yyyy-MM-dd");
        }
        if (endTime == null) {
            endTime = DateUtil.getDateByNumber(0, "yyyy-MM-dd");
        }
        if (dayType == null) {
            dayType = 0;
        }

        String yesterdayStartTime;
        String yesterdayEndTime;
        //获取时间间隔
        Integer days = DateUtil.getDayBySubtract(endTime, startTime, "yyyy-MM-dd");
        String temp = " 00:05:00";
        //判断日期类型
        if (dayType == 0) {
            //按天查找
            if (days <= 1 && DateUtil.isToday(startTime)) {
                //如果为今天实时数据
                startTime = DateUtil.getDateByNumber(0, "yyyy-MM-dd 00:05:00");
                endTime = DateUtil.getDateByNumber(0, "yyyy-MM-dd HH:mm:ss");
                yesterdayStartTime = DateUtil.getDateByNumber(-1, "yyyy-MM-dd 00:05:00");
                yesterdayEndTime = DateUtil.getDateByNumber(-1, "yyyy-MM-dd HH:mm:ss");
            } else {
                startTime += temp;
                endTime += temp;
                yesterdayStartTime = DateUtil.newgetDateByNumber(-days, "yyyy-MM-dd", startTime)+temp;
                yesterdayEndTime = startTime;
            }
        } else {
            //按月查找
            startTime += temp;
            endTime += temp;
            yesterdayStartTime = DateUtil.newgetbyMonthDateByNumber(-1, "yyyy-MM-dd", startTime)+temp;
            yesterdayEndTime = DateUtil.newgetbyMonthDateByNumber(-1, "yyyy-MM-01", endTime)+temp;
        }
        System.out.println("[批次系列API]开始时间:" + startTime);
        System.out.println("结束时间:" + endTime);
        System.out.println("上周期开始时间:" + yesterdayStartTime);
        System.out.println("上周期结束时间:" + yesterdayEndTime);
        //开启线程异步处理
        CountDownLatch countDownLatch = new CountDownLatch(2);
        //第一个子线程获取本周期数据
//        ExecutorService es1 = Executors.newFixedThreadPool(5);

        final List<BusinessSeriesData>[] todayBusinessDataList = new List[4];
        String finalStartTime = startTime;
        String finalEndTime = endTime;
        int finalDayType = dayType;
        executor.execute(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + "执行开始");
                if ("**********".equals(phone) || "**********".equals(phone) || "**********".equals(phone)) {
                    batchDataService.initFour(finalStartTime, finalEndTime);
                } else {
                    batchDataService.init(finalStartTime, finalEndTime);
                }
                todayBusinessDataList[0] = batchDataService.getWarehouseDataList();
                todayBusinessDataList[1] = batchDataService.getAreaDataList();
                todayBusinessDataList[2] = batchDataService.getBatchIndexDataTotal();
                todayBusinessDataList[3] = batchDataService.getShopDataList();
            } catch (Exception e) {
                System.out.println(Thread.currentThread().getName() + "异常:");
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "执行成功");
            countDownLatch.countDown();
        });
        //第二个子线程获取上周期数据
        final List<BusinessSeriesData>[] yesterdayBusinessDataList = new List[4];
        executor.execute(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + "执行开始");
                if ("**********".equals(phone) || "**********".equals(phone) || "**********".equals(phone)) {
                    batchDataService.initFour(finalStartTime, finalEndTime);
                } else {
                    batchDataService.init(yesterdayStartTime, yesterdayEndTime);
                }
                yesterdayBusinessDataList[0] = batchDataService.getWarehouseDataList();
                yesterdayBusinessDataList[1] = batchDataService.getAreaDataList();
                yesterdayBusinessDataList[2] = batchDataService.getBatchIndexDataTotal();
                yesterdayBusinessDataList[3] = batchDataService.getShopDataList();
            } catch (Exception e) {
                System.out.println(Thread.currentThread().getName() + "异常:");
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "执行成功");
            countDownLatch.countDown();
        });

        System.out.println("等待两个线程执行完毕…… ……");
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("两个子线程都执行完毕,继续执行主线程");

        List<BusinessSeriesData> shopDataList = indexDataManager.calculateIncreaseData(todayBusinessDataList[3], yesterdayBusinessDataList[3]);
        List<BusinessSeriesData> warehouseDataList = indexDataManager.calculateIncreaseData(todayBusinessDataList[0], yesterdayBusinessDataList[0]);
        List<BusinessSeriesData> areaDataList = indexDataManager.calculateIncreaseData(todayBusinessDataList[1], yesterdayBusinessDataList[1]);
        List<BusinessSeriesData> totalDataList = indexDataManager.calculateIncreaseData(todayBusinessDataList[2], yesterdayBusinessDataList[2]);

        HashMap<String, Object> result = new HashMap<>();
        result.put("shopDataList", shopDataList);
        result.put("warehouseDataList", warehouseDataList);
        result.put("areaDataList", areaDataList);
        result.put("totalDataList", totalDataList);
        System.out.println("[批次系列API]结束:" + startTime);
        return ResponseUtil.ok(result);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值