Spring @Async无法使用专用线程池执行问题排查
问题:Spring @Async方法未并行执行导致测试耗时超预期
原本预期测试代码仅耗时1秒,但实际花费了5秒,Spring无法让返回Future的方法每次调用都在不同线程执行,原因是什么?
测试代码
@SpringJUnitConfig(SpringAsyncTest.MyTestConfig::class) class SpringAsyncTest { @Autowired lateinit var sut: MySpecialBean @TestConfiguration() class MyTestConfig { @Bean(name = ["myThreadPoolExecutor"]) fun asyncExecutor(): ThreadPoolTaskExecutor { val executor = ThreadPoolTaskExecutor() executor.corePoolSize = 10 executor.queueCapacity = 2000 executor.initialize() return executor } @Bean fun sut(): MySpecialBean = MySpecialBean() } @Test @Throws(InterruptedException::class, ExecutionException::class) fun testSpringAsync() { CompletableFuture.allOf( sut.aFutureCompletable(), sut.aFutureCompletable(), sut.aFutureCompletable(), sut.aFutureCompletable(), sut.aFutureCompletable() ).get() } }
被测类(SUT)
@Component class MySpecialBean { @Throws(InterruptedException::class) @Async("myThreadPoolExecutor") // 使用自定义线程池 fun aFutureCompletable(): CompletableFuture<Void?> { Thread.sleep(1000) // 模拟耗时任务 return CompletableFuture.completedFuture(null) } }
问题原因
核心问题是Spring @Async的生效依赖动态代理:Spring通过为容器管理的bean创建代理对象来注入异步调用逻辑,只有当方法通过代理bean调用时,@Async注解才会生效。
你在测试配置的sut()方法中直接通过new MySpecialBean()创建实例,这个对象没有经过Spring的代理增强,@Async注解完全不起作用,所有方法调用都是同步执行的。5次调用每次耗时1秒,总耗时自然是5秒。
修复方案
方案1:让Spring自动扫描创建代理实例
移除测试配置中手动创建MySpecialBean的@Bean方法,在测试类上添加@ComponentScan注解,让Spring扫描并创建带代理的bean:
@SpringJUnitConfig(SpringAsyncTest.MyTestConfig::class) @ComponentScan(basePackageClasses = [MySpecialBean::class]) // 添加扫描注解 class SpringAsyncTest { // ... 其余代码不变 }
方案2:通过Spring容器获取代理实例
如果需要保留@Bean方法,不要直接new,而是通过Spring容器获取已被代理的实例:
@Autowired private lateinit var applicationContext: ApplicationContext @Bean fun sut(): MySpecialBean { return applicationContext.getBean(MySpecialBean::class.java) }
验证方式
可以在aFutureCompletable()方法中添加线程名称打印,验证是否异步执行:
@Async("myThreadPoolExecutor") fun aFutureCompletable(): CompletableFuture<Void?> { println("当前执行线程:${Thread.currentThread().name}") // 添加线程打印 Thread.sleep(1000) return CompletableFuture.completedFuture(null) }
修复后会看到输出多个不同的线程池线程名称(如myThreadPoolExecutor-1、myThreadPoolExecutor-2等),未修复时则全是主线程名称。
内容的提问来源于stack exchange,提问作者oligofren
相关产品推荐
相关产品推荐

