两种并发模式

发布时间:2026/6/25 20:58:34
两种并发模式 1. 线程池异步Fire and Forgetcom.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 线程分两种用户线程非守护、守护线程DaemonJVM退出规则只要还有任意一条用户线程在运行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. 准备任务列表 ListEchoxCallableAutoInsCarHisDTO callables new ArrayList(...); try { // 2. Fork每辆车包装成一个独立任务 response.getCarHisLis().forEach(context - { callables.add(new EchoxCallableAutoInsCarHisDTO() { Override public AutoInsCarHisDTO doCall() throws Exception { return fillCarInfo(request, context); // 每个任务干的活 } }); }); // 3. Join批量并发执行 等待所有结果 ListAutoInsCarHisDTO carHisDTOList AsyncUtils.batchCall(callables, callables.size()); response.setCarHisLis(carHisDTOList); // 拿到全部结果再赋值 } catch (Exception e) { // 4. 降级并发出问题就退回单线程串行 fillCarInfoWithIsvSync(request, response); } }耗时基础数据5 个子任务耗时100、300、200、500、150ms串行执行所有任务依次执行总耗时 全部任务耗时求和 计算100300200500150 1250ms充足线程并发线程数≥任务数所有任务同时启动总耗时 所有任务耗时最大值木桶效应由最慢任务决定 计算max (100,300,200,500,150) 500ms核心结论串行总耗时任务耗时累加充足线程并发总耗时取任务最长耗时并发优势大幅缩短整体执行时间本例耗时从 1250ms 降至 500ms原理并发性能受耗时最长的任务限制木桶效应