新闻详情

新闻详情

首页 / 资讯中心 / 详情

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

发布时间:2026/9/4 19:14:15来源:尧图网络
@Async + 自定义线程池,一个”阻塞拒绝策略”差点让 Tomcat 死掉
Spring Boot 项目里做异步任务大多数人都会选 Async。简单、好用、跟 Spring 深度集成。但线程池的拒绝策略很少有人认真想过。最近 review 一段代码差点被一个”聪明的”设计坑到。场景主请求之外的副作用操作有一个核心接口必须在几百毫秒内返回。但每次调用还附带三个副作用操作写历史记录、写变更日志、写操作记录。这仨操作跟主流程结果无关但都需要写同一个 MySQL。直觉方案异步。让主线程赶紧返回三个写操作扔到后台线程去做。实现Async 自定义线程池于是有了这样的设计upgradeCorePoolSize: 6 upgradeMaxPoolSize: 40 upgradeQueueCapacity: 8000core6日常流量够了。突发时能扩展到 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 的异常会静默丢失SpringAsync 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()); }改成CompletableFutureT返回值让调用方可选地感知异步结果是更好的做法。2. 线程池负载不可观测队列当前深度多少活跃线程数多少有没有发生过拒绝没有任何指标暴露出来。当队列悄悄积压到 7999距离死锁只差一步没有任何告警。3. 新线程拿不到请求上下文Async在新线程中执行ThreadLocal里的东西比如当前请求的用户信息、traceId全是空的。这个项目通过显式传参解决——每个异步方法都仔细列出需要的参数。这能 work但不优雅。每新增一个异步方法都要梳理一遍参数依赖。漏一个就等着 NPE。总结维度本方案更好的做法拒绝策略queue.put() 阻塞调用方CallerRunsPolicy 或 DiscardPolicy DLQ异常处理Async void 隐式丢失CompletableFutureT callback可观测性无指标Micrometer 暴露队列深度、活跃线程上下文传递ThreadLocal null手动传参参数显式传递该方案已在做或异步上下文传播框架异步任务的核心矛盾是既要异步不阻塞调用方又要可靠不丢任务、异常可感知。Async void天生偏向异步牺牲了可观测和异常传播。自定义queue.put()想补”可靠”这一端结果补出了更大的问题。正确做法拒绝策略用CallerRunsPolicy——保底方案不会死锁暴露线程池指标——在队列积压到 80% 时就发出告警如果要更高的可靠性换消息队列Kafka/RabbitMQ——有磁盘持久化和死信机制不是线程池能替代的去看看你项目里的Async线程池拒绝策略是什么
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

光模块封装代工厂采购真空共晶炉:核心参数与工程实践解析 2026/9/5 0:02:53

光模块封装代工厂采购真空共晶炉:核心参数与工程实践解析

在光通信模块的封装车间里,LD芯片与陶瓷基板之间的焊接空洞率若超过3%,器件在高温老化测试中的失效概率将成倍上升。这是每一位工艺工程师在处理光模块封装代工厂采购真空共晶炉方案时,最先面对的硬指标。空洞不仅阻碍热传导,更会…

阅读更多 →
向量检索与标量过滤混合查询:PostgreSQL pgvector 与 Milvus 的过滤下推实操 2026/9/5 0:02:53

向量检索与标量过滤混合查询:PostgreSQL pgvector 与 Milvus 的过滤下推实操

向量检索与标量过滤混合查询:PostgreSQL pgvector 与 Milvus 的过滤下推实操在构建企业级 RAG 知识库与智能体记忆检索时,极少有纯粹的“全局高维向量相似度检索”。 绝大多数真实的业务查询都带有极其严格的标量业务过滤条件(Scalar Filteri…

阅读更多 →
幂等性设计:在 Agent 自动重试与工具执行中的防重复扣费实战 2026/9/5 0:02:53

幂等性设计:在 Agent 自动重试与工具执行中的防重复扣费实战

幂等性设计:在 Agent 自动重试与工具执行中的防重复扣费实战在智能体(Agent)系统的自动化执行过程中,“自动重试(Auto-Retry)”与“自纠错反思(Self-Correction)”是保障任务高成功率…

阅读更多 →
流式背压机制:避免前端渲染卡死与内存暴涨的滑动窗口限流 2026/9/5 0:02:53

流式背压机制:避免前端渲染卡死与内存暴涨的滑动窗口限流

流式背压机制:避免前端渲染卡死与内存暴涨的滑动窗口限流在大模型流式输出(Streaming)与智能体实时推流的架构中,生产环境中经常出现一种“上下游生产消费速率严重失衡”的极端情况: 生产端极速产出:大模型…

阅读更多 →
niri 笔记本低功耗调优:3 步把待机功耗从 8W 压到 5W 2026/9/4 23:59:53

niri 笔记本低功耗调优:3 步把待机功耗从 8W 压到 5W

niri 笔记本低功耗调优:3 步把待机功耗从 8W 压到 5W 【免费下载链接】niri A scrollable-tiling Wayland compositor. 项目地址: https://gitcode.com/GitHub_Trending/ni/niri 在 niri 这款 Wayland 合成器(负责把每个窗口画面拼合成最终屏幕图…

阅读更多 →
自托管PaaS实战:Docker Compose+Caddy搭建部署平台 2026/9/4 23:59:53

自托管PaaS实战:Docker Compose+Caddy搭建部署平台

很多开发者刚开始接触 Vercel、Railway 这类平台时,往往会被它们的自动化程度惊艳到:项目推到 Git 仓库,平台自动完成构建、发布、HTTPS 证书签发,甚至还能一键创建数据库和定时任务。这种体验确实流畅,基本把“部署”…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞