Async 用的好好的突然全变同步执行自定义线程池 三个坑一次讲清导读项目里把短信、简历解析这些耗时操作丢给 Async 异步执行某天加了自定义线程池配置后所有异步方法全部退化成同步执行接口响应时间直接翻倍。排查下来是 Async 和线程池配置的几个经典坑叠一起了。这篇把这套东西讲透。基础Async 怎么用才对先看最基础的正确用法——异步方法和调用方不能在同一个类里自调用会失效后面细说ServicepublicclassResumeParseService{Async(asyncExecutor)publicvoidparseResumeAsync(LongresumeId){// 简历解析耗时操作resumeParser.parse(resumeId);}}RestControllerpublicclassResumeController{AutowiredprivateResumeParseServiceparseService;PostMapping(/resume/upload)publicResultupload(RequestBodyMultipartFilefile){LongresumeIdsaveFile(file);// 这里调的是另一个 bean 的方法代理生效parseService.parseResumeAsync(resumeId);returnResult.ok(resumeId);}}加上EnableAsync启用异步就能跑。但注意——一旦你自定义了线程池很多配置就变了。自定义线程池核心参数怎么定简历解析的异步线程池我这样配的ConfigurationEnableAsyncpublicclassAsyncConfig{Bean(asyncExecutor)publicThreadPoolTaskExecutorasyncExecutor(){ThreadPoolTaskExecutorexecutornewThreadPoolTaskExecutor();executor.setCorePoolSize(4);executor.setMaxPoolSize(8);executor.setQueueCapacity(200);executor.setThreadNamePrefix(async-);executor.setRejectedExecutionHandler(newThreadPoolExecutor.CallerRunsPolicy());executor.initialize();returnexecutor;}}几个参数的选择逻辑核心线程 4机器 4 核简历解析是 IO 密集型可以再高些队列 200限制任务堆积防止内存爆掉拒绝策略 CallerRuns队列满了让调用线程自己跑宁可慢一点也不能丢任务。坑一Async 自调用异步完全失效现象在ResumeService内部一个方法调另一个带Async的方法日志里看到异步方法还是同步执行。原因Spring 的 Async 靠代理实现this 调用不走代理。this.parseAsync()相当于直接调原始对象的方法注解自然不生效。解决异步方法放到独立 Service 里注入调用或者注入自身代理Autowired自己再调。最简单是拆类。坑二异步方法里事务不生效现象异步方法里有Transactional注解但异常没回滚数据半截写进去了。原因Async 和 Transactional 是两套代理方法被异步代理先拦截直接调了目标方法事务代理在更外层才生效事务压根没挂上。另外异步线程和调用线程不在一个线程事务的线程绑定也对不上。解决事务逻辑放在异步方法内部再调一个事务方法或者干脆把事务和异步分开——异步方法只做调度事务方法内部独立ServicepublicclassNotifyService{Async(asyncExecutor)publicvoidsendNotifyAsync(LongorderId){// 事务方法在另一个 bean 里代理正常inboxService.saveAndUpdateStatus(orderId);}}坑三异常被吞排查全靠日志翻现象运营反馈简历解析明明失败了页面上却提示成功。简历解析接口返回 200但解析服务在后台线程里实际抛了 NPE。排查先查接口日志——接口确实是同步返回成功的异步本来就不阻塞。再翻解析服务的业务日志发现完全找不到异常堆栈只有方法入口和出口的普通日志。这就很奇怪NPE 不会凭空消失。定位Async 方法抛出的异常默认交给 Spring 的AsyncUncaughtExceptionHandler处理不配置的话异常直接丢弃连日志都不打。异步线程和调用线程是两个线程调用方线程根本接不到异常接口自然成功。解决配置全局异常处理器把异步异常完整打出来ConfigurationEnableAsyncpublicclassAsyncConfigimplementsAsyncConfigurer{OverridepublicAsyncUncaughtExceptionHandlergetAsyncUncaughtExceptionHandler(){return(ex,method,params)-log.error(异步方法执行异常: method{}, params{},method.getName(),params,ex);}}配完之后异步异常至少能打到日志里不至于石沉大海。可直接复用的清单Async 别在同类内自调用拆到独立 Bean自定义线程池时**核心线程按 CPU 核数 ×IO 倍数**估队列别塞满内存异步和事务分开事务方法放独立 Bean必须配AsyncUncaughtExceptionHandler否则异步异常全被吞拒绝策略选CallerRunsPolicy任务不丢但调用线程兜底执行这几个坑我前后踩了一个星期配置改了又改才捋清楚。核心记忆点Async 的代理、事务的代理、线程池的配置三者是独立的谁也别指望谁自动兜底。