Spring @Async多线程异常:首次正常后续线程挂起问题求助
Spring Boot @Async 嵌套调用导致线程池死锁的排查与解决
首先直接定位核心问题:你的@Async嵌套调用共用了同一个线程池,引发了循环等待的死锁。
问题根源拆解
结合你提供的线程dump和线程池状态数据,具体逻辑如下:
- ClassA的
foo和ClassB的bar都标记了@Async,且默认复用同一个ABCService线程池。 - 当
foo方法被调用时,会占用线程池的1000个核心线程,随后在方法内部提交大量bar异步任务到同一线程池。 - 此时所有核心线程都被
foo方法占用,这些foo线程卡在CompletableFuture.allOf().join()步骤,等待bar任务完成;但bar任务要么在队列排队,要么等待线程池扩容,却没有空闲线程能执行bar任务——形成死锁闭环:foo等bar完成,bar等foo释放线程。
线程dump的栈信息也验证了这一点:所有ABCService线程都在CompletableFuture.join()处处于WAITING(parking)状态,完全僵住。
再看线程池状态数据:
- 首次处理后,
poolSize一直保持1000(默认allowCoreThreadTimeOut=false,核心线程不会自动回收) - 第二次请求时,
foo再次占满1000个核心线程,提交的bar任务只能进入队列(剩余队列容量从5000降至3680),但无空闲线程处理任务,导致activeCount始终停在1000不动。
可行解决方案
方案1:为不同层级@Async分配独立线程池(推荐)
最彻底的解决方式是让foo和bar使用不同线程池,避免资源抢占。
第一步:配置多线程池
@Configuration @EnableAsync public class AsyncConfiguration { // 给ClassA的foo方法专用的调度线程池 @Bean(name = "fooExecutor") public ThreadPoolTaskExecutor fooExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(200); // 无需过大,仅用于调度任务 executor.setMaxPoolSize(500); executor.setQueueCapacity(1000); executor.setThreadNamePrefix("FooExecutor-"); executor.initialize(); return executor; } // 给ClassB的bar方法用的执行线程池,复用你原有的参数 @Bean(name = "barExecutor") public ThreadPoolTaskExecutor barExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(1000); executor.setMaxPoolSize(20000); executor.setQueueCapacity(5000); executor.setThreadNamePrefix("BarExecutor-"); executor.initialize(); return executor; } }
第二步:在@Async注解指定线程池
// ClassA的foo方法绑定fooExecutor @Async("fooExecutor") public void foo(String param1, String param2, List<?> list, String param4, boolean param5){ try { List<CompletableFuture<String>> completableFutureList = new ArrayList<>(); for (Object item : list){ CompletableFuture<String> response = classB.bar(param1, param2, item, param4, param5); completableFutureList.add(response); } CompletableFuture.allOf(completableFutureList.toArray(new CompletableFuture[0])).join(); // 你的后续业务逻辑 } catch (Throwable e) { logger.error("Exception occurred in foo method", e); } } // ClassB的bar方法绑定barExecutor @Async("barExecutor") public CompletableFuture<String> bar(String param1, String param2, Object item, String param4, boolean param5){ try{ // 你的耗时业务逻辑 return CompletableFuture.completedFuture("success"); } catch(Exception e) { logger.error("Exception occurred in bar method", e); // 必须返回failedFuture,避免join时抛出未捕获异常 return CompletableFuture.failedFuture(e); } }
方案2:移除bar方法的@Async(适合非耗时场景)
如果bar方法的业务逻辑并不耗时,可直接去掉其@Async注解,让它在foo的线程中同步执行。这样不会产生新的异步任务,自然避免线程池资源抢占问题。
该方案最简单,但前提是bar的执行时间足够短,不会拖慢foo的整体效率。
方案3:调整线程池参数(不推荐,治标不治本)
你可以尝试调大核心线程数或队列容量,但这只是暂时缓解问题——当请求量进一步增大时,死锁仍会出现。且过多线程会导致CPU上下文切换开销剧增,降低系统性能。
额外注意事项
- 异常处理要完整:
bar方法返回CompletableFuture时,务必在catch块返回failedFuture,否则join()会抛出未捕获异常,引发线程中断等问题。 - 监控线程池状态:引入Spring Boot Actuator依赖,通过
/actuator/threadpool端点实时监控线程池的活跃线程数、队列容量等指标,便于及时发现问题。
内容的提问来源于stack exchange,提问作者user2163603
相关产品推荐
相关产品推荐

