news 2026/6/25 18:47:04

@Async + 自定义线程池,一个”阻塞拒绝策略”差点让 Tomcat 死掉

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
@Async + 自定义线程池,一个”阻塞拒绝策略”差点让 Tomcat 死掉

Spring Boot 项目里做异步任务,大多数人都会选 @Async。简单、好用、跟 Spring 深度集成。但线程池的拒绝策略,很少有人认真想过。

最近 review 一段代码,差点被一个”聪明的”设计坑到。

场景:主请求之外的副作用操作

有一个核心接口,必须在几百毫秒内返回。但每次调用还附带三个副作用操作:写历史记录、写变更日志、写操作记录。这仨操作跟主流程结果无关,但都需要写同一个 MySQL。

直觉方案:异步。让主线程赶紧返回,三个写操作扔到后台线程去做。

实现:@Async + 自定义线程池

于是有了这样的设计:

upgradeCorePoolSize: 6 upgradeMaxPoolSize: 40 upgradeQueueCapacity: 8000

core=6,日常流量够了。突发时能扩展到 40。队列 8000 充当缓冲区。

@Bean public TaskExecutor upgradeHandlerExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(6); executor.setMaxPoolSize(40); executor.setQueueCapacity(8000); executor.setKeepAliveSeconds(30); executor.setThreadFactory(new ThreadFactoryBuilder() .setNameFormat("async-pool-%d").build()); // 拒绝策略:阻塞,不放弃 executor.setRejectedExecutionHandler((r, executor) -> { try { executor.getQueue().put(r); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); executor.setWaitForTasksToCompleteOnShutdown(true); return executor; }

调用方无感:

@Async("upgradeHandlerExecutor") public void asyncSaveHistory(SomeContext context) { saveHistory(context.getEntity()); saveChangeLog(context.getEntityId(), context.getChangeInfo()); } // 调用方不关心结果 upgradeHandlerService.asyncSaveHistory(context);

看起来不错。主线程不阻塞,异步线程池兜住流量,queue.put()保证任务永不丢失。

问题:拒绝策略反压

标准ThreadPoolExecutor有四种拒绝策略:

策略行为风险
AbortPolicy抛异常调用方收到异常
CallerRunsPolicy调用方线程自己跑调用方变慢
DiscardPolicy静默丢弃消息丢失
DiscardOldestPolicy丢弃最老的消息丢失

这个项目选了一个自定义的queue.put()。当线程池满载、队列填满时,提交任务的线程会被阻塞在put()上——直到队列有空位。

问题来了:调用方是谁?

Tomcat 线程 (200 个) → Controller → Service.asyncSaveHistory() → ThreadPoolTaskExecutor.execute() → queue.put() ← 阻塞! // Tomcat 线程卡死了

当数据库写入变慢(比如突然 8000 辆车同时升级),40 个异步线程全部卡在 INSERT 上,队列积压到 8000。第 8001 个请求进来,put()阻塞——Tomcat 线程被钉死在等待队列上。一个阻塞,两个阻塞,很快 200 个 Tomcat 线程全堵在这里。

原本用 @Async 就是为了不让数据库写入阻塞 Tomcat,结果拒绝策略反而把阻塞传导回去了。

对比:CallerRunsPolicy 反而更好

// 如果换成这个 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

当队列满了,调用方线程(Tomcat 线程)自己执行异步任务。虽然这只 Tomcat 线程变慢了(它得跑完 INSERT 才能处理下一个请求),但至少:

  • 它释放了队列中的一个位置
  • 它推进了任务处理
  • 它不会让所有 Tomcat 线程同时死锁

“慢”比”死”好一万倍。

还有三个隐藏问题

1. @Async void 的异常会静默丢失

Spring@Async void方法里抛出的异常,默认行为是:log 一下,然后消失。调用方永远感知不到。

这个项目里给每个异步方法加了 try-catch,但如果有未捕获的异常冒出来——比如某个 Service 内部抛了 NPE——没有人知道。

@Async("upgradeHandlerExecutor") public void asyncSaveHistory(SomeContext context) { // 如果这行抛 NPE,调用方不知道 saveHistory(context.getEntity()); // context.getEntity() 可能为 null saveChangeLog(context.getEntityId(), context.getChangeInfo()); }

改成CompletableFuture<T>返回值,让调用方可选地感知异步结果,是更好的做法。

2. 线程池负载不可观测

队列当前深度多少?活跃线程数多少?有没有发生过拒绝?没有任何指标暴露出来。

当队列悄悄积压到 7999,距离死锁只差一步,没有任何告警。

3. 新线程拿不到请求上下文

@Async在新线程中执行,ThreadLocal里的东西(比如当前请求的用户信息、traceId)全是空的。

这个项目通过显式传参解决——每个异步方法都仔细列出需要的参数。这能 work,但不优雅。每新增一个异步方法都要梳理一遍参数依赖。漏一个就等着 NPE。

总结

维度本方案更好的做法
拒绝策略queue.put() 阻塞调用方CallerRunsPolicy 或 DiscardPolicy + DLQ
异常处理@Async void 隐式丢失CompletableFuture<T> + callback
可观测性无指标Micrometer 暴露队列深度、活跃线程
上下文传递ThreadLocal = null,手动传参参数显式传递(该方案已在做)或异步上下文传播框架

异步任务的核心矛盾是:既要异步(不阻塞调用方),又要可靠(不丢任务、异常可感知)。@Async void天生偏向异步,牺牲了可观测和异常传播。自定义queue.put()想补”可靠”这一端,结果补出了更大的问题。

正确做法:

  1. 拒绝策略用CallerRunsPolicy——保底方案,不会死锁
  2. 暴露线程池指标——在队列积压到 80% 时就发出告警
  3. 如果要更高的可靠性,换消息队列(Kafka/RabbitMQ)——有磁盘持久化和死信机制,不是线程池能替代的

去看看你项目里的@Async线程池,拒绝策略是什么?

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/25 18:45:32

GitHub已收录!2026最新Java岗面试题大全(最全+答案)

进大厂是大部分程序员的梦想&#xff0c;而进大厂的门槛也是比较高的&#xff0c;所以这里整理了一份阿里、美团、滴滴、头条等大厂面试大全&#xff0c;对于 Java 后端的朋友来说应该是最全面最完整的面试备战仓库&#xff0c;为了更好地整理每个模块&#xff0c;我也参考了很…

作者头像 李华
网站建设 2026/6/25 18:40:34

冷挤压工艺在汽车锻件制造中的应用与实践——53 年老牌工厂

摘要&#xff1a;本文结合浙江三维大通精锻股份有限公司 53 年冷挤压技术实践经验&#xff0c;系统介绍冷挤压工艺在汽车锻件制造中的关键技术要点、质量控制体系及设备选型方案。文章涵盖空气悬架配件、喷油器体、电机轴等典型产品的工艺参数优化&#xff0c;为从事汽车零部件…

作者头像 李华
网站建设 2026/6/25 18:33:33

Mythos推理增强中间件:可验证AI推理的工程化实践

1. 项目概述&#xff1a;这不是一次普通更新&#xff0c;而是一次能力边界的实质性突破“TAI #200: Anthropic’s Mythos Capability Step Change and Gated Release”这个标题里藏着三个关键信号&#xff1a;编号#200说明这是The AI Alignment Newsletter&#xff08;TAI&…

作者头像 李华
网站建设 2026/6/25 18:32:34

3步轻松搞定知网文献批量下载:告别繁琐手动操作的高效方案

3步轻松搞定知网文献批量下载&#xff1a;告别繁琐手动操作的高效方案 【免费下载链接】CNKI-download :frog: 知网(CNKI)文献下载及文献速览爬虫 (Web Scraper for Extracting Data) 项目地址: https://gitcode.com/gh_mirrors/cn/CNKI-download 还在为毕业论文需要下…

作者头像 李华
网站建设 2026/6/25 18:32:18

GQE:给GQA自注意力装上MoE,一半查询头就够

Grouped Query Experts: Mixture-of-Experts on GQA Self-Attention 作者&#xff1a;Vishesh Tripathi, Abhay Kumar 核心发表机构&#xff1a;FrontiersMind 论文链接&#xff1a;arXiv:2606.20945v2 发布于&#xff1a;arXiv 预印本&#xff08;cs.LG&#xff09; | :— | :…

作者头像 李华
网站建设 2026/6/25 18:28:28

计算机毕业设计之基于微信小程序主持接单程序的设定

研究背景源于移动互联网技术的快速发展和主持行业对高效、便捷接单方式的需求。技术实现上&#xff0c;该程序采用un-app框架进行前端开发&#xff0c;结合Springboot后端框架和MySQL数据库&#xff0c;实现了前后端分离的开发模式。功能实现方面&#xff0c;该程序提供了主持人…

作者头像 李华