当前位置: 首页 > news >正文

两种并发模式

1. 线程池异步(Fire and Forget)

com.alipay.autoinsprod.biz.insure.service.impl.InstSimQuoteServiceImpl#asyncSimQuote

用户点击「试算报价」

前端 ──→ 调用 asyncSimQuote() ──→ 后端主线程:校验参数 + 提交任务到线程池 ──→ 立即返回成功

│ │

▼ ▼

前端收到响应 ──→ 展示"试算中"文案 子线程:调保司接口 ──→ 结果写入数据库

前端开始轮询 ──→ 调查询接口 ──→ 查数据库看结果出来没

├── 没出来 → 继续轮询

└── 出来了 → 展示试算结果

ThreadPoolExecutor 7 个入参完整详解

new ThreadPoolExecutor( 2, // 1. corePoolSize 核心线程数 4, // 2. maximumPoolSize 最大线程数 60L, TimeUnit.SECONDS, // 3. keepAliveTime + 4. unit 空闲存活时长单位 new LinkedBlockingQueue<>(100), // 5. workQueue 阻塞任务队列 new ThreadFactoryBuilder()...build(), // 6. threadFactory 线程工厂 new ThreadPoolExecutor.CallerRunsPolicy() //7. handler 拒绝策略 )

1. corePoolSize = 2 核心线程数

线程池常驻存活的线程数量,不会被空闲回收。

  1. 新任务进来时,当前运行线程数 < 2:立刻新建核心线程执行任务;
  2. 核心线程默认永久存活,不受 60 秒空闲时间限制(除非开启allowCoreThreadTimeOut);
  3. 业务场景:模拟报价常驻需要 2 个线程处理常规流量。

2. maximumPoolSize = 4 最大线程数

线程池能创建的总线程上限:总线程 = 核心线程 + 非核心临时工线程

  • 核心 2 条,最大 4 条 → 最多可以创建4 - 2 = 2条临时线程;
  • 触发创建临时线程条件:核心线程全部繁忙 + 任务队列已满。

3. keepAliveTime = 60L、4. unit = TimeUnit.SECONDS

非核心临时工线程的空闲超时时间

  1. 临时工线程 60 秒没有接到新任务,会自动销毁释放资源;
  2. 核心线程不受这个时间约束,一直保留在线程池;
  3. 单位:秒,也可以用MILLISECONDS毫秒、MINUTES分钟。

5. workQueue = LinkedBlockingQueue<>(100) 任务阻塞队列

存放等待执行任务的队列,容量固定 100。

任务分配完整流程(重点)
  1. 任务提交,线程数 < 核心数 2 → 创建核心线程执行;
  2. 核心线程全部忙满(2 条都在干活)→ 任务放入这个队列排队,最多存 100 个任务;
  3. 队列存满 100 个任务后,还有新任务进来 → 开始创建临时线程,最多开到 4 条;
  4. 线程总数已经 4 条、队列也满 100,再来新任务 → 触发拒绝策略。

LinkedBlockingQueue:无界队列变种,这里手动限制容量 100,生产环境必须限制队列长度,防止 OOM。

6. ThreadFactory 线程工厂

用来统一创建线程,这里使用guava ThreadFactoryBuilder配置:

  • setNameFormat("sim-quote-%d"):给线程命名sim-quote-0、sim-quote-1,打印日志、排查堆栈时能快速区分是模拟报价线程池;
  • setDaemon(true):设置为守护线程;JVM 退出时不会等待这个线程池任务执行完毕,直接关闭,适合后台报价异步任务;
  • 不自定义工厂会使用默认工厂,线程名字无业务含义,不方便线上排查问题。

7. RejectedExecutionHandler 拒绝策略:CallerRunsPolicy

线程池总线程达到最大值 4 + 队列 100 已满,新提交任务无法处理时执行的策略:

CallerRunsPolicy 逻辑

不会丢弃任务、不会抛出异常,把任务交给提交这个任务的主线程同步执行。 举个例子:主线程调用SIM_QUOTE_EXECUTOR.submit()提交任务,线程池满载后,这个任务不再异步执行,直接在主线程同步跑。

优缺点
  • 优点:不会丢失任何报价任务,无任务丢失风险;天然限流,主线程被占用,上游提交速度自然降下来;
  • 缺点:主线程会被阻塞,如果主线程是 HTTP 接口线程,会导致接口响应变慢、超时。

线程

Java 线程分两种:用户线程(非守护)、守护线程(Daemon)

JVM退出规则

只要还有任意一条用户线程在运行,JVM 就不会关闭;

只剩守护线程时,JVM 直接强制退出,不会等守护线程跑完。

1. 普通业务线程(默认Daemon = false,用户线程)

你不写setDaemon(true),创建出来的都是用户线程。 场景举例:接口请求主线程、订单处理线程。 进程关闭时,如果这种线程还在跑,JVM 会卡住,等待它执行完毕才停机。

2. setDaemon (true) = 守护线程

守护线程是辅助后台线程,作用是给用户线程打工,比如定时日志、监控、缓存刷新。 当项目停止(Spring 关闭、服务下线): 如果主线程 / 业务线程全部结束,哪怕守护线程任务没跑完,JVM 直接关掉,不会阻塞停机流程。

完整任务执行流程梳理

  1. 提交报价任务;
  2. 运行线程 < 2 → 新建核心线程执行;
  3. 已有 2 条核心线程都在工作 → 任务放入队列,队列未满 100 就排队;
  4. 队列存满 100 个任务 → 创建临时线程,总线程不超过 4;
  5. 线程 4 条全部忙碌 + 队列 100 满员 → 新任务由调用者主线程同步执行。

该线程池参数设计总结

  1. 常驻 2 个线程处理日常报价;
  2. 突发流量最多扩容到 4 个临时线程;
  3. 最多缓冲 100 个等待报价任务;
  4. 临时线程闲置 60 秒自动销毁,释放资源;
  5. 线程带业务名称、守护线程,便于运维;
  6. 满载不丢任务,交给调用线程执行,保证报价不丢失。
/* * Ant Group * Copyright (c) 2004-2026 All Rights Reserved. */ package com.alipay.autoinsprod.biz.insure.service.impl; /** * @author guangtaochen.cgt * @version InstSimQuoteServiceImpl.java, v 0.1 2026年04月21日 下午5:01 guangtaochen.cgt */ public class InstSimQuoteServiceImpl implements InstSimQuoteService { private static final Logger LOGGER = LoggerFactory.getLogger(LoggerNames.BIZ_INSURE); /** * 异步报价专用线程池 * 使用守护线程避免阻塞JVM退出 */ private static final ExecutorService SIM_QUOTE_EXECUTOR = new ThreadPoolExecutor( 2, // 核心线程数 4, // 最大线程数,应对突发流量 60L, TimeUnit.SECONDS, // 空闲线程存活时间 new LinkedBlockingQueue<>(100), // 任务队列容量 new ThreadFactoryBuilder().setNameFormat("sim-quote-%d").setDaemon(true).build(), new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:调用者线程执行 ); @OsgiReference private InstSimQuoteDomainService instSimQuoteDomainService; @Override public void asyncSimQuote(AutoSimQuote autoSimQuote,AutoInsContext ctx) { //参数校验 paramCheck(autoSimQuote); // 在主线程中提前提取上下文数据,避免子线程 ThreadLocal 隔离问题 final AutoInsEnquiry enquiry = ctx.getAutoInsEnquiry(); final AutoInsQuote quote = ctx.getAutoInsQuote(); final AutoUserCarHis carHis = ctx.getAutoUserCarHis(); AutoSimQuote simQuote = ctx.getSimQuote(); SIM_QUOTE_EXECUTOR.execute(new TracerRunnable() { @Override public void doRun() { long startTime = System.currentTimeMillis(); boolean success = true; try { // 在子线程中重新设置上下文数据 AutoInsContext subCtx = AutoInsContext.getInstance(); if (enquiry != null) { subCtx.setAutoInsEnquiry(enquiry); } if (quote != null) { subCtx.setAutoInsQuote(quote); } if (carHis != null) { subCtx.setAutoUserCarHis(carHis); } if (simQuote != null) { subCtx.setSimQuote(simQuote); } instSimQuoteDomainService.singleInstSimQuote(autoSimQuote,subCtx); } catch (Exception e) { success = false; LogUtil.error(e, LOGGER, "[试算报价]执行异常,simQuoteId={0}", autoSimQuote.getSimQuoteId()); } finally { long costTime = System.currentTimeMillis() - startTime; LogUtil.info(LOGGER, "[试算报价]执行完成,success={0},耗时={1}ms,simQuoteId={2}", success, costTime, autoSimQuote.getSimQuoteId()); } } }); } /** * 参数校验 * * @param autoSimQuote */ private void paramCheck(AutoSimQuote autoSimQuote) { AssertUtil.notNull(autoSimQuote, AUTO_SIM_QUOTE_IS_NULL, "autoSimQuote is null"); AssertUtil.notNull(autoSimQuote.getApplyInfo(), AUTO_SIM_QUOTE_APPLY_INFO_IS_NULL, "applyInfo is null"); AssertUtil.notNull(autoSimQuote.getApplyInfo().getSimQuotePackage(), AUTO_SIM_QUOTE_PLAN_IS_NULL, "simQuotePackage is null"); } }

2. 并发执行(Fork and Join)

并发执行:把一个大任务拆成多个独立的子任务,丢给多个线程同时跑,主线程等所有子任务都完成后再汇总结果继续往下走。

private void fillCarInfoWithIsvAsync(QueryCarHisListRequest request, QueryCarHisListResponse response) { // 1. 准备任务列表 List<EchoxCallable<AutoInsCarHisDTO>> callables = new ArrayList<>(...); try { // 2. Fork:每辆车包装成一个独立任务 response.getCarHisLis().forEach(context -> { callables.add(new EchoxCallable<AutoInsCarHisDTO>() { @Override public AutoInsCarHisDTO doCall() throws Exception { return fillCarInfo(request, context); // 每个任务干的活 } }); }); // 3. Join:批量并发执行 + 等待所有结果 List<AutoInsCarHisDTO> carHisDTOList = AsyncUtils.batchCall(callables, callables.size()); response.setCarHisLis(carHisDTOList); // 拿到全部结果再赋值 } catch (Exception e) { // 4. 降级:并发出问题就退回单线程串行 fillCarInfoWithIsvSync(request, response); } }

耗时

基础数据

5 个子任务耗时:100、300、200、500、150ms

  1. 串行执行:所有任务依次执行,总耗时 = 全部任务耗时求和 计算:100+300+200+500+150 = 1250ms
  2. 充足线程并发(线程数≥任务数):所有任务同时启动,总耗时 = 所有任务耗时最大值(木桶效应,由最慢任务决定) 计算:max (100,300,200,500,150) = 500ms

核心结论

  1. 串行总耗时:任务耗时累加;充足线程并发总耗时:取任务最长耗时
  2. 并发优势:大幅缩短整体执行时间,本例耗时从 1250ms 降至 500ms
  3. 原理:并发性能受耗时最长的任务限制(木桶效应)


http://www.jsqmd.com/news/1077202/

相关文章:

  • 当“散装物料”遇上“智慧装车”:工厂里的装车,也可以很智能
  • 国内冷镦钢厂主要分布在哪些产区?
  • QQ音乐加密文件终极解密指南:3步快速解锁qmcdump工具完整教程
  • 如何免费激活Unity全版本:UniHacker跨平台破解工具完整指南
  • roop-unleashed终极指南:5分钟上手专业级AI换脸工具,轻松实现深度伪造
  • 网络安全领域探索指南
  • 出钱做游戏,版权到底归谁?90%的人都搞错了
  • XUnity自动翻译器完全指南:解锁Unity游戏多语言体验的终极方案
  • LSTM序列分类实战:时间步建模、门控调优与工程落地
  • Proxmox 备份恢复:VM 和 LXC 恢复指南、PBS 恢复局限及 Veeam 补充方案
  • LangFlow深度解析:构建企业级AI工作流的可视化开发平台
  • PMI 监控链路里的 pmistore,一条容易被忽略却非常关键的 HTTP Destination
  • 【计算机毕业设计案例】基于 Java 的智慧物业便民服务系统设计与实现(程序+文档+讲解+定制)
  • 使用 C# 提取 Word 文档中的表格数据
  • 【STM32HAL库开发】学习笔记(1)——GPIO
  • 解密Wallpaper Engine创意工坊下载器:Flutter技术栈下的高效壁纸管理方案
  • HACS高级故障诊断与系统优化深度解析:架构级解决方案实战
  • 计算机毕业设计之基于文本挖掘的综艺评论情感分析--LW
  • 操作系统调优
  • 资料丨2026版《医疗器械生产质量管理规范》自查报告
  • MPC8360E PCI总线深度解析:信号时序、事务终止与调试实践
  • Eclipse Cyclone DDS实战:从构建、配置到性能调优的机器人核心中间件指南
  • CogVideo与CogVideoX模型结构
  • 越权漏洞实战挖掘:从原理到案例,掌握水平与垂直越权防御
  • 125、 PCIE交换机仲裁与带宽分配:从一次深夜调试说起
  • 2025年中国AI验布机五强格局深度盘点:从百家争鸣到五强割据,谁在真正解决纺织企业的验布之痛?
  • 计算机毕业设计之基于Java的影视创作论坛的设计与实现
  • 国茂减速机传动轴故障全解析:键槽磨损、轴弯曲、轴颈划伤维修指南
  • MySQL(二)数据定义语言DDL、数值类型、字符串类型、日期时间类型详细讲解
  • PaperXie AI PPT 生成器:网页端一键出稿,学术答辩汇报不用再熬夜排版