Spring Boot 3.5虚拟线程环境下@Async导致控制器阻塞的修复方案咨询
我完全理解你遇到的困扰——启用虚拟线程后,原本期望通过@Async异步执行的服务方法,居然让控制器阻塞了30秒才返回响应,而切换回普通ThreadPoolTaskExecutor时一切正常。咱们来一步步拆解问题原因,然后给出针对性的修复方案。
问题根源分析
你的核心问题出在**AsyncConfig中自定义的taskExecutor实现上**,具体是这段代码的逻辑错误:
Executor virtualThreadExecutor = task -> { Runnable runnable = threadContextDecorator.decorate(task); try (var executor = Executors.newThreadPerTaskExecutor(threadFactory)) { executor.execute(runnable); } };
这里的try-with-resources语法是“罪魁祸首”:Executors.newThreadPerTaskExecutor()返回的ExecutorService实现了AutoCloseable接口,当try代码块执行完毕时,会自动调用close()方法。而这个close()方法会先调用shutdown(),然后等待所有已提交的任务执行完成,这就导致你的@Async任务被强制同步执行了——控制器必须等30秒的Thread.sleep()完成后,才能继续执行返回响应的逻辑。
而当你使用普通ThreadPoolTaskExecutor时,它的execute()方法是真正异步的,不会等待任务完成,所以控制器能立即返回。
修复方案
我们有两种简洁的修复方式,推荐优先使用Spring官方提供的虚拟线程任务执行器:
方案1:使用Spring 3.2+原生VirtualThreadTaskExecutor(推荐)
Spring 3.2及以上版本专门提供了VirtualThreadTaskExecutor来简化虚拟线程的异步任务配置,无需手动处理ExecutorService的生命周期:
@Configuration @EnableAsync @RequiredArgsConstructor public class AsyncConfig { private final ThreadContextDecorator threadContextDecorator; @Bean public TaskExecutor taskExecutor() { // 创建虚拟线程工厂 ThreadFactory virtualThreadFactory = Thread.ofVirtual() .name("async-executor-", 0) .factory(); // 使用Spring原生的虚拟线程任务执行器 VirtualThreadTaskExecutor taskExecutor = new VirtualThreadTaskExecutor(virtualThreadFactory); // 配置MDC上下文装饰器 taskExecutor.setTaskDecorator(threadContextDecorator); return taskExecutor; } }
方案2:修正自定义Executor的实现
如果你需要手动控制ExecutorService的生命周期,可以修改原有的配置,避免使用try-with-resources强制等待任务完成:
@Configuration @EnableAsync @RequiredArgsConstructor public class AsyncConfig { private final ThreadContextDecorator threadContextDecorator; private ExecutorService virtualThreadExecutor; @Bean public TaskExecutor taskExecutor() { ThreadFactory threadFactory = Thread.ofVirtual() .name("async-executor-", 0) .factory(); // 直接创建虚拟线程的per-task执行器,不要用try-with-resources virtualThreadExecutor = Executors.newThreadPerTaskExecutor(threadFactory); ConcurrentTaskExecutor taskExecutor = new ConcurrentTaskExecutor(virtualThreadExecutor); taskExecutor.setTaskDecorator(threadContextDecorator); return taskExecutor; } // 优雅关闭执行器,避免应用 shutdown 时残留任务 @PreDestroy public void shutdownExecutor() { if (virtualThreadExecutor != null && !virtualThreadExecutor.isShutdown()) { virtualThreadExecutor.shutdown(); try { // 等待任务完成,超时则强制关闭 if (!virtualThreadExecutor.awaitTermination(60, TimeUnit.SECONDS)) { virtualThreadExecutor.shutdownNow(); } } catch (InterruptedException e) { virtualThreadExecutor.shutdownNow(); Thread.currentThread().interrupt(); } } } }
验证修复效果
修改配置后,重新启动应用测试:
- 发送请求到
/hello,控制器会立即返回Hello, World!,不会再阻塞30秒; - 后台日志会在30秒后打印
Hello service completed的内容; - 查看线程信息,会发现
HelloService的代码确实运行在虚拟线程中。
额外注意事项
- 你的
ThreadContextDecorator实现是正确的,它能将主线程的MDC上下文复制到虚拟线程中,执行完成后自动清除,避免内存泄漏; - 确保你的Spring Boot版本是3.2及以上(你用的是3.5,满足要求),
VirtualThreadTaskExecutor是3.2才引入的; @Async方法返回CompletableFuture<Void>的写法是规范的,无需修改。
内容来源于stack exchange




