You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring @Async多线程异常:首次正常后续线程挂起问题求助

Spring Boot @Async 嵌套调用导致线程池死锁的排查与解决

首先直接定位核心问题:你的@Async嵌套调用共用了同一个线程池,引发了循环等待的死锁。

问题根源拆解

结合你提供的线程dump和线程池状态数据,具体逻辑如下:

  1. ClassA的foo和ClassB的bar都标记了@Async,且默认复用同一个ABCService线程池。
  2. 当foo方法被调用时,会占用线程池的1000个核心线程,随后在方法内部提交大量bar异步任务到同一线程池。
  3. 此时所有核心线程都被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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:27:07