多线程场景的学习3,使用CountDownLatch

1、CountDownLatch的用法

其核心机制是通过一个计数器实现线程等待,计数归零的时候,释放所有线程。

创建计数器:创建时指定正整数(如 N),表示需要等待的线程数量或任务数。

子线程完成任务后调用 countDown(),计数器减 1。

主线程调用 await() 阻塞,直到计数器归零后才会继续执行主线程代码。⁠⁣ ⁠⁣

2、CountDownLatch 和 Future的get方法区别

Future的get方法也可以实现线程等待,但它是针对单个任务(线程),且get方法是顺序阻塞的。比如,有多个任务,调用get方法会顺序获取,就算第二个线程早就执行完了,也会等待第一个Future的get方法拿到返回之后,才会执行第二个Future的get方法。

而CountDownLatch是针对一组任务,他不关心具体线程,只重视一组任务(线程)的结果。

3、场景

假设,预订三间房间。从 【预订】 --> 【预定成功/失败】,每一间房肯定有不少的校验。最后,全部预订成功了,才会发送邮件,有一间房预订失败了,就发送具体失败哪一间房。

这个时候,就可以使用 CountDownLatch ,将【预订】到【预定成功/失败】的这个流程单独弄成异步的,然后主流程等待异步线程全部结束,在执行发邮件的流程。

4、代码实现

返回结果定义

java 复制代码
public class BookRoomResult {
    private String success;
    private String roomId; //房间号(唯一键)
    private String errorMsg;

    public BookRoomResult(String success, String roomId, String errorMsg) {
        this.success = success;
        this.roomId = roomId;
        this.errorMsg = errorMsg;
    }

    public String getSuccess() {
        return success;
    }

    public void setSuccess(String success) {
        this.success = success;
    }

    public String getRoomId() {
        return roomId;
    }

    public void setRoomId(String roomId) {
        this.roomId = roomId;
    }

    public String getErrorMsg() {
        return errorMsg;
    }

    public void setErrorMsg(String errorMsg) {
        this.errorMsg = errorMsg;
    }
}

多线程模拟

java 复制代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;

public class ThreadPoolExecutorDemo1 {
    //核心线程数,核心线程默认会一直存在
    public static int corePoolSize = 5;

    //最大线程数
    public static int maximumPoolSize = 15;

    //非核心线程的线程存活时间
    public static int keepAliveTime = 2;


    public static void main(String[] args) {
        ThreadPoolExecutor executor = buildThreadPool();
        List<String> room = Arrays.asList("room1", "room2", "room3");

        //有多少间房,就创建多大的计数器
        CountDownLatch latch = new CountDownLatch(room.size());

        List<Future<BookRoomResult>> result = new ArrayList<>();
        for (int i = 0; i < room.size(); i++) {
            try {
                int finalI = i;
                Future<BookRoomResult> submit = executor.submit(new Callable() {
                    @Override
                    public BookRoomResult call() {
                        try {
                            if (finalI == 2) {
                                Thread.sleep(2000);
                                return new BookRoomResult("fail", room.get(finalI), "预订失败,客房满了");
                            }
                        } catch (Exception e) {
                            System.out.println("异步处理发生未知异常");
                        } finally {
                            //每个线程内的任务执行完毕后,计数器减 1
                            latch.countDown();
                        }
                        return new BookRoomResult("success", room.get(finalI), "预订成功");
                    }
                });
                result.add(submit);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        try {
            //主线程阻塞10s,等待计数器归零
            //10秒内如果异步线程全部执行完毕了,那就走正常流程
            if (latch.await(10, TimeUnit.SECONDS)) {
                //到这一步了,说明每个线程的任务执行完毕,用get方法肯定是不会出现阻塞
                for (Future<BookRoomResult> bookRoom : result) {
                    if ("fail".contains(bookRoom.get().getSuccess())) {
                        System.out.println("存在失败房间:" + bookRoom.get().getRoomId() + "失败原因:" + bookRoom.get().getErrorMsg());
                    }
                }
            } else {
                System.out.println("10s内没有收到全部的异步线程返回,疑似网络出问题");
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        System.out.println("==================主线程执行完毕===================");
    }

    /**
     * 定义一个线程池
     *
     * @return
     */
    public static ThreadPoolExecutor buildThreadPool() {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                keepAliveTime, TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(10), //有界队列,容量10
                Executors.defaultThreadFactory(),
                (r, executor1) -> System.out.println("线程超过最大线程数"));
        return executor;
    }
}
相关推荐
无限大.2 小时前
验证码对抗史
java·开发语言·python
June`2 小时前
C++11(四):特殊类与单例模式设计精要
开发语言·c++
代码不行的搬运工2 小时前
面向RDMA网络的Swift协议
开发语言·网络·swift
明月别枝惊鹊丶2 小时前
【C++】GESP 三级手册
java·开发语言·c++
毕设源码-钟学长2 小时前
【开题答辩全过程】以 公交线路查询系统为例,包含答辩的问题和答案
java
梵得儿SHI2 小时前
SpringCloud - 核心组件精讲:Nacos 深度解析(服务注册 + 配置中心一站式实现)
java·spring boot·spring cloud·nacos·微服务架构的核心组件·服务注册发现与配置管理·nacos的核心原理与实战应用
不如打代码KK2 小时前
Java SPI与Spring Boot SPI的区别
java·开发语言·spring boot
非凡的小笨鱼2 小时前
利用arthas查看java服务里指定对象的大小
java·spring·arthas
代码or搬砖2 小时前
自定义注解全面详解
java·开发语言