多线程场景的学习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;
    }
}
相关推荐
蛋蛋的就会顺顺的6 小时前
hot100 p0——栈,贪心算法,动态规划
java·前端·javascript·算法·leetcode·力扣
需要8267 小时前
Spring Bean 生命周期:从实例化到销毁的完整钩子
java·后端·spring
小 黄 鸡7 小时前
HashMap 面试必会知识点详解(JDK 8)
java·面试·职场和发展
死磕音视频7 小时前
让小模型做选择题:限定候选项并读取 logits 概率的入门实践(附 Python 代码)
开发语言·python·大模型·文本分类·transformers
伯恩bourne9 小时前
Spring Boot Bean 注册的两种方式:@Component 家族 vs @Configuration + @Bean
java·spring boot·后端
silvamux_9 小时前
curl 测试 OpenAI 兼容 API 的完整流程
java·人工智能·缓存·ai·oneapi
省钱兄--zs9 小时前
深圳24小时自助健身房软硬件解决方案实战指南与开发流程详解
java·数据仓库·spring boot·小程序·架构
顽疲10 小时前
Java vue 养老系统源码详解:月账单 care_month_bill 与 Quartz 扣费 Job 实践
java·开发语言·vue.js·vue养老·springboot养老·java养老
Wang's Blog10 小时前
Java 项目实战: 交友项目 - 安装开发工具与环境准备
java·开发语言·交友
用户212968352616810 小时前
开源了一个 Java 多智能体 LLM 框架 Baton:一个依赖,把 Agent 循环、工具调用、记忆、编排全配齐
java·llm