一次请求打几个下游,这个Java并行任务工具类6个方法按序聚合

📌 高并发系统设计 : 基于Redis与MQ的秒杀系统架构:库存预减、防超卖与限流防刷全流程解析

📋 目录

一、工具类功能

一组 Supplier 并行提交到指定线程池,按提交顺序聚合结果,支持整体超时与单任务异常隔离,把「一次请求要打好几个下游」收成一次调用。

二、所需环境

JDK 17或以上

三、方法与调用示例(按需调用)

🔢 方法序号:1

🎯 方法用途:

  • 把一组 Supplier 并行提交到你指定的线程池,按提交顺序把结果收回来
  • 一次请求要打多个下游(订单、用户、库存)时用,不用自己逐个 supplyAsync 再拼 allOf

🧩 方法定义 :public static <T> List<T> supplyAll(List<Supplier<T>> tasks, Executor executor)

🗣️ 入参解释 :public static <T> List<T> supplyAll(任务列表, 线程池)

📤 返回值解释 :List<T>,与提交顺序一致的结果列表,不可变

💡 边界口径:

  • 严格模式:任一任务失败就整体以 CompletionException 失败,cause 是原始异常
  • 任一任务失败时仍会等其他任务跑完才返回,避免别的线程里已发生的副作用失去归属
  • executor 为 null 直接拒绝,不允许用 ForkJoinPool.commonPool() 跑阻塞任务
  • 任务列表会先整体校验再提交,含 null 元素抛 IllegalArgumentException,不会留下没人管的任务

🧪 方法示例:

  • supplyAll(List.of(订单任务, 用户任务, 库存任务), executor) 拿到 [订单SO20261005001, 用户-张三, 库存-剩8件],耗时 83 毫秒而串行要 120 毫秒
  • 三个任务按 60/40/20 毫秒提交,最先完成的库存仍排在第三位,拿到 [订单SO20261005001, 用户-张三, 库存-剩8件]
  • supplyAll(tasks, null) 抛 IllegalArgumentException,消息是「线程池不能为null(禁止使用 ForkJoinPool.commonPool() 跑阻塞任务)」
  • 任务列表里放一个 null 元素,抛 IllegalArgumentException: 任务列表含null元素,无法执行

🔢 方法序号:2

🎯 方法用途:

  • 并行执行整组任务并给一个整体超时,到点就不再等下去
  • 下游有慢风险时用,避免一个卡住的接口把这次请求拖成长尾

🧩 方法定义 :public static <T> List<T> supplyAll(List<Supplier<T>> tasks, Duration timeout, Executor executor)

🗣️ 入参解释 :public static <T> List<T> supplyAll(任务列表, 整体超时, 线程池)

📤 返回值解释 :List<T>,按提交顺序的结果列表

💡 边界口径:

  • 超时后取消所有还没出结果的 future,让下游依赖与内存及时释放,但正在跑的任务不会被打断
  • 超时以 CompletionException 失败,cause 是 TimeoutException,消息写明等待多久
  • timeout 必须大于 0,给 Duration.ZERO 抛 IllegalArgumentException
  • 真要可控中断,任务内部得自己响应 Thread.currentThread().isInterrupted()

🧪 方法示例:

  • supplyAll(tasks, Duration.ofSeconds(1), executor) 拿到 [订单SO20261005001, 用户-张三, 库存-剩8件],与不带超时版本同序
  • 整体超时给 100 毫秒而慢任务要跑更久时抛 CompletionException,cause 是 TimeoutException,消息是「并行任务在 100 毫秒内未全部完成」
  • 严格模式抛错前等满了 75 毫秒,因为是慢任务跑完才返回
  • 等待线程被中断时抛 CompletionException,cause 拿到 java.lang.InterruptedException
  • supplyAll(tasks, Duration.ZERO, executor) 抛 IllegalArgumentException,消息带上「当前为PT0S」

🔢 方法序号:3

🎯 方法用途:

  • 隔离模式并行执行:任务失败不抛异常,按下标记进 ParallelResult
  • 允许部分下游挂掉的聚合(订单查到就先展示,优惠券查不到当没有)用它

🧩 方法定义 :public static <T> ParallelResult<T> supplyAllIsolated(List<Supplier<T>> tasks, Executor executor)

🗣️ 入参解释 :public static <T> ParallelResult<T> supplyAllIsolated(任务列表, 线程池)

📤 返回值解释 :ParallelResult<T>,带下标的逐项结果,可查 isAllSuccess()、failureCount()、resultAt(int)

💡 边界口径:

  • 只给「成功列表 + 异常列表」排不了障,所以每一项都保留下标,resultAt(int) 取回成功值或失败原因
  • 任务失败不抛异常,由调用方看 isAllSuccess() 决定是否降级
  • successes() 与 failures() 返回的是不可变视图
  • 线程池与任务列表的校验口径同方法1

🧪 方法示例:

  • 中间那个下游抛「优惠券服务无响应」,isAllSuccess() 拿到 false
  • 同一份结果的 failureCount() 拿到 1
  • 同一份结果的 successes() 拿到 [订单SO20261005001, 库存-剩8件],失败项没有把整组带走
  • resultAt(1) 的 isSuccess() 拿到 false,error 是 优惠券服务无响应
  • resultAt(0) 的 value 拿到 订单SO20261005001
  • failures() 拿到 [优惠券服务无响应],异常对象本身还在,能继续取 cause

🔢 方法序号:4

🎯 方法用途:

  • 隔离模式带整体超时:等到点就把没结果的项按真实原因记账
  • 既不能整体失败、又不能无限等时用这一版

🧩 方法定义 :public static <T> ParallelResult<T> supplyAllIsolated(List<Supplier<T>> tasks, Duration timeout, Executor executor)

🗣️ 入参解释 :public static <T> ParallelResult<T> supplyAllIsolated(任务列表, 整体超时, 线程池)

📤 返回值解释 :ParallelResult<T>,超时项也带真实原因的逐项结果

💡 边界口径:

  • 整体超时、任务失败、等待线程被中断是三种不同原因,逐项记的 error 各不相同,不谎报成同一种
  • 超时只让调用方不再等,任务本身继续跑到自然结束
  • 已完成的任务结果不会被超时连累,照常留在对应下标上

🧪 方法示例:

  • 整体超时 200 毫秒时 resultAt(0) 拿到 用户-张三,先完成的结果没被连累
  • 同一份结果的 resultAt(1) 的 error 类型拿到 TimeoutException,消息是「并行任务在 200 毫秒内未全部完成」
  • 等待线程被中断时,未完成项的 error 拿到 java.lang.InterruptedException,不会被写成超时

🔢 方法序号:5

🎯 方法用途:

  • 给一批输入并行跑同一段处理逻辑,输出顺序与入参一致
  • 批量查详情、批量调外部接口这类 map 型任务用它,比 parallelStream 更可控

🧩 方法定义 :public static <I, O> List<O> mapAll(List<I> inputs, Function<I, O> mapper, Executor executor)

🗣️ 入参解释 :public static <I, O> List<O> mapAll(输入集合, 单元处理逻辑, 线程池)

📤 返回值解释 :List<O>,与入参同序的输出列表

💡 边界口径:

  • 顺序按入参下标对齐,跟哪个先完成无关
  • 任一元素处理失败即整体以 CompletionException 失败,要部分成功就用方法3 的隔离模式
  • 输入集合含 null 元素抛 IllegalArgumentException,线程池为 null 同样拒绝

🧪 方法示例:

  • mapAll(List.of("SO-3", "SO-1", "SO-2"), this::queryDetail, executor) 拿到 [SO-3=已支付, SO-1=已支付, SO-2=已支付],与入参顺序一致
  • 三个订单号耗时不同,返回值仍按入参下标对齐,拿到 [SO-3=已支付, SO-1=已支付, SO-2=已支付]
  • 输入里放一个 null,抛 IllegalArgumentException: 输入集合含null元素,无法作为任务参数

🔢 方法序号:6

🎯 方法用途:

  • 批量并行处理的同时给一个整体超时
  • 输入条数不确定、又怕个别元素卡住整批时用这一版

🧩 方法定义 :public static <I, O> List<O> mapAll(List<I> inputs, Function<I, O> mapper, Duration timeout, Executor executor)

🗣️ 入参解释 :public static <I, O> List<O> mapAll(输入集合, 单元处理逻辑, 整体超时, 线程池)

📤 返回值解释 :List<O>,与入参同序的输出列表

💡 边界口径:

  • 超时口径同方法2:到点不再等,但不打断正在执行的任务
  • 超时以 CompletionException 失败,cause 是 TimeoutException
  • timeout 必须大于 0,mapper 为 null 抛 IllegalArgumentException

🧪 方法示例:

  • mapAll(orders, this::queryDetail, Duration.ofSeconds(1), executor) 拿到 [SO-3=已支付, SO-1=已支付, SO-2=已支付]
  • 超时时间给得短于最慢元素时抛 CompletionException,cause 是 TimeoutException,消息写明等待毫秒数

四、工具类源码(可直接复制)

📦 源码文件

相关推荐
CarIise1 小时前
JavaWeb 文件上传的四种实现方案
1024程序员节
长欢喜一场1 小时前
豆包:字节跳动AI助手的全方位透视
人工智能·搜索引擎·1024程序员节
4SAPI1 小时前
搭建 AI 数据分析工具大模型 API 选型指南
数据库·人工智能·架构·php·1024程序员节
KingOTC1 小时前
国产模块测评:基于国产芯片的实际表现
rfid·1024程序员节
小嘉丶学长1 小时前
Day04 · 程序入门
1024程序员节
Alsn861 小时前
Windows 安装 Ollama 及本地模型部署
学习·1024程序员节
霂鳯1 小时前
新手勇闯网络安全 | MySQL基础(二)
1024程序员节
俊哥V2 小时前
每日 AI 研究简报 · 2026-10-24
人工智能·ai·1024程序员节
burg_xun2 小时前
【生命科学 Agent】推理平台(三)
人工智能·系统架构·1024程序员节