多线程场景的学习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;
    }
}
相关推荐
SimonKing20 小时前
SpringBoot整合秘笈:让Mybatis用上Calcite,实现统一SQL查询
java·后端·程序员
日月云棠2 天前
各版本JDK对比:JDK 25 特性详解
java
用户8307196840822 天前
Spring Boot 项目中日期处理的最佳实践
java·spring boot
JavaGuide2 天前
Claude Opus 4.6 真的用不起了!我换成了国产 M2.5,实测真香!!
java·spring·ai·claude code
IT探险家2 天前
Java 基本数据类型:8 种原始类型 + 数组 + 6 个新手必踩的坑
java
花花无缺2 天前
搞懂new 关键字(构造函数)和 .builder() 模式(建造者模式)创建对象
java
用户908324602732 天前
Spring Boot + MyBatis-Plus 多租户实战:从数据隔离到权限控制的完整方案
java·后端
桦说编程2 天前
实战分析 ConcurrentHashMap.computeIfAbsent 的锁冲突问题
java·后端·性能优化
程序员清风2 天前
用了三年AI,我总结出高效使用AI的3个习惯!
java·后端·面试
beata2 天前
Java基础-13: Java反射机制详解:原理、使用与实战示例
java·后端