两种并发模式
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 核心线程数
线程池常驻存活的线程数量,不会被空闲回收。
- 新任务进来时,当前运行线程数 < 2:立刻新建核心线程执行任务;
- 核心线程默认永久存活,不受 60 秒空闲时间限制(除非开启
allowCoreThreadTimeOut); - 业务场景:模拟报价常驻需要 2 个线程处理常规流量。
2. maximumPoolSize = 4 最大线程数
线程池能创建的总线程上限:总线程 = 核心线程 + 非核心临时工线程
- 核心 2 条,最大 4 条 → 最多可以创建
4 - 2 = 2条临时线程; - 触发创建临时线程条件:核心线程全部繁忙 + 任务队列已满。
3. keepAliveTime = 60L、4. unit = TimeUnit.SECONDS
非核心临时工线程的空闲超时时间
- 临时工线程 60 秒没有接到新任务,会自动销毁释放资源;
- 核心线程不受这个时间约束,一直保留在线程池;
- 单位:秒,也可以用
MILLISECONDS毫秒、MINUTES分钟。
5. workQueue = LinkedBlockingQueue<>(100) 任务阻塞队列
存放等待执行任务的队列,容量固定 100。
任务分配完整流程(重点)
- 任务提交,线程数 < 核心数 2 → 创建核心线程执行;
- 核心线程全部忙满(2 条都在干活)→ 任务放入这个队列排队,最多存 100 个任务;
- 队列存满 100 个任务后,还有新任务进来 → 开始创建临时线程,最多开到 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 直接关掉,不会阻塞停机流程。
完整任务执行流程梳理
- 提交报价任务;
- 运行线程 < 2 → 新建核心线程执行;
- 已有 2 条核心线程都在工作 → 任务放入队列,队列未满 100 就排队;
- 队列存满 100 个任务 → 创建临时线程,总线程不超过 4;
- 线程 4 条全部忙碌 + 队列 100 满员 → 新任务由调用者主线程同步执行。
该线程池参数设计总结
- 常驻 2 个线程处理日常报价;
- 突发流量最多扩容到 4 个临时线程;
- 最多缓冲 100 个等待报价任务;
- 临时线程闲置 60 秒自动销毁,释放资源;
- 线程带业务名称、守护线程,便于运维;
- 满载不丢任务,交给调用线程执行,保证报价不丢失。
/* * 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
- 串行执行:所有任务依次执行,总耗时 = 全部任务耗时求和 计算:100+300+200+500+150 = 1250ms
- 充足线程并发(线程数≥任务数):所有任务同时启动,总耗时 = 所有任务耗时最大值(木桶效应,由最慢任务决定) 计算:max (100,300,200,500,150) = 500ms
核心结论
- 串行总耗时:任务耗时累加;充足线程并发总耗时:取任务最长耗时
- 并发优势:大幅缩短整体执行时间,本例耗时从 1250ms 降至 500ms
- 原理:并发性能受耗时最长的任务限制(木桶效应)
