Springboot @Async不执行原因分析

这篇博客探讨了在Spring Boot中使用@Async注解进行异步任务处理时遇到的问题。当默认线程池被耗尽,可能导致任务无法执行。解决方案包括调整业务代码、自定义线程池和优化异步方法的调用方式。同时,提到了@Async失效的可能原因,如缺少@EnableAsync注解、方法权限和调用方式等,并提供了相应的解决建议。

背景

项目中有一个异步方法运行一段时间后就不再执行了。该异步方法是使用@Async注解的。

分析原因

  1. 业务场景用了@Async注解的方法,且没有使用自定义线程池。
  2. ThreadPoolTaskExecutor是springboot提供的默认线程池 。也就是说如果没有自定义线程池,那么会自动装配这个默认的。

In the absence of an Executor bean in the context, Spring Boot auto-configures a ThreadPoolTaskExecutor with sensible defaults that can be automatically associated to asynchronous task execution (@EnableAsync) and Spring MVC asynchronous request processing.

  1. ThreadPoolTaskExecutor的默认参数是由TaskExecutionProperties控制的。默认核心线程是8,线程prefix是 “task-”。 搜索线程dump,有8个异步任务已经耗尽了线程池,且执行没有停止的迹象。

构造默认线程池方法
org.springframework.boot.task.TaskExecutorBuilder#configure
在这里插入图片描述

  1. 如果该线程池被耗尽,则所有任务将会在线程池的队列中等待,而默认队列大小是Integer.MAX_VALUE

  2. 一方面要调整业务代码,使用默认线程池;另一方面也可以暂时增加默认线程池大小缓解
    在这里插入图片描述

@Async使用总结

1、 非必须不使用异步。如果是核心业务包含事务处理,先同步记录数据,再异步发起。防止直接发起后异步没被调度到服务关闭,造成数据丢失
2、 核心业务必须使用自定义线程池,防止默认异步线程池阻塞,也方便通过线程池监控核心执行情况
3、 简单业务场景使用默认线程池时,避免操作长时间过长占用默认线程池,影响Spring以及其它框架组件异步任务执行
4、 @Async可能失效的原因
	a) @SpringBootApplication启动类当中没有添加@EnableAsync注解。
	b) 异步方法使用注解@Async的返回值只能为void或者Future。
	c) 没有走Spring的代理类。因为@Transactional和@Async注解的实现都是基于Spring的AOP,而AOP的实现是基于动态代理模式实现的。那么注解失效的原因就很明显了,有可能因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器管理。
@Async失效解决方法
	a) 注解的方法必须是public方法。
	b) 注解的方法不要定义为static
	c) 方法一定要从另一个类中调用,也就是从类的外部调用,类的内部调用是无效的。
	d) 如果需要从类的内部调用,需要先获取其代理类。
<think>我们正在讨论在Spring Boot中同时使用@Transactional和@Async注解是否会导致事务生效的问题。 根据引用[2]:在同一个类中,一个方法调用另外一个有注解(比如@Async,@Transactional)的方法,注解是会生效的。这是因为Spring的AOP代理机制,自调用(即同一个类中的一个方法调用另一个方法)会绕过代理,导致增强逻辑(如事务、异步)执行。 同时使用@Transactional和@Async需要注意: 1. 这两个注解都是基于Spring AOP实现的,因此它们都受到Spring AOP代理机制的限制。 2. 如果在一个类中,一个没有注解的方法调用同一个类中带有@Transactional和@Async的方法,那么这两个注解都会失效(因为自调用没有经过代理对象)。 3. 另外,@Async注解的方法默认使用SimpleAsyncTaskExecutor,它会在新线程中执行方法。而事务是通过ThreadLocal来传递的,因此在新线程中无法获取到原事务上下文,这会导致事务生效。 但是,如果两个注解分别用于同的方法,并且这些方法被外部类调用(即通过代理对象调用),那么它们各自都会生效。然而,如果同一个方法同时使用这两个注解,则可能会出现问题: - @Async会创建一个新线程,而事务上下文(TransactionSynchronizationManager管理的资源)是基于ThreadLocal的,因此在新线程中事务上下文将可用,导致事务无法传播。 因此,结论是: - 如果在一个方法上同时使用@Transactional和@Async,那么事务将会生效,因为异步执行在新线程中,而事务上下文无法跨线程传播。 - 如果两个注解分别用于同的方法,并且避免自调用问题(即通过代理调用),那么它们各自可以生效,但是要注意在异步方法中调用事务方法时,事务方法必须是通过代理调用的(即能是自调用),并且事务上下文会传播到异步方法中。 解决方案: 1. 避免在同一个类中自调用(即调用本类中带有注解的方法),应该将带有注解的方法放到另一个Bean中,然后通过注入该Bean来调用。 2. 避免在同一个方法上同时使用@Transactional和@Async。如果确实需要异步执行事务操作,可以将事务操作放在异步方法内部的一个单独方法(在另一个Bean中)上,并确保该方法是事务性的。 例如: ```java @Service public class MyService { @Autowired private AnotherService anotherService; @Async public void asyncMethod() { // 这里调用另一个Bean的事务方法 anotherService.transactionalMethod(); } } @Service public class AnotherService { @Transactional public void transactionalMethod() { // 事务操作 } } ``` 这样,异步方法通过调用另一个Bean的事务方法,事务就会生效,因为它是通过代理调用的,并且事务上下文在同一个线程内(因为事务方法是在异步方法调用的同一个线程中执行的,即异步线程)。 但是注意:在异步方法中调用事务方法时,事务方法会在异步线程中执行,此时事务上下文是新建的(因为原线程的事务上下文会传播到新线程),所以这个事务是独立的事务。 总结:同时使用@Transactional和@Async时,如果处理当(如同一个方法上同时使用、自调用问题、线程隔离问题),会导致事务生效。需要按照上述方案进行设计。 引用[5]中提到的事务提交过程,也说明了事务是基于当前线程的,跨线程则无法使用同一个事务。 因此,回答用户的问题:在Spring Boot中同时使用@Transaction和@Async可能会导致事务生效,主要原因是线程切换导致事务上下文丢失以及自调用问题。需要合理设计代码结构来避免。 生成相关问题: 1. 如何解决Spring中自调用导致的注解失效问题? 2. 在Spring Boot中,如何确保异步方法中的事务操作正确执行? 3. Spring事务传播机制在异步方法中如何工作? 4. 除了将方法移到另一个Bean中,还有哪些方法可以解决自调用问题? 5. 在异步方法中开启新事务与在原有事务中执行有何区别?</think>在Spring Boot中同时使用`@Transactional`和`@Async`注解**可能导致事务生效**,核心问题在于线程切换和代理机制。以下是详细分析和解决方案: --- ### 一、问题原因 1. **线程隔离性** `@Async`会将方法提交到新线程执行,而Spring事务依赖`ThreadLocal`存储事务上下文。新线程无法继承原线程的事务上下文,导致事务失效[^5]。 *示例代码:* ```java @Async @Transactional // 事务在新线程中失效 public void asyncTransactionalMethod() { // 数据库操作 } ``` 2. **自调用问题** 若在同一个类中调用`@Async`+`@Transactional`方法,会绕过Spring代理,导致两个注解都失效[^2]: ```java public void caller() { asyncTransactionalMethod(); // 自调用,注解失效 } ``` --- ### 二、解决方案 #### 方案1:拆分到同Bean(推荐) ```java @Service class ServiceA { @Autowired private ServiceB serviceB; // 注入其他Bean public void caller() { serviceB.asyncTransactionalMethod(); // 通过代理调用 } } @Service class ServiceB { @Async @Transactional // 此时事务生效 public void asyncTransactionalMethod() { // 数据库操作 } } ``` **原理**:跨Bean调用通过Spring代理执行,激活AOP增强。 #### 方案2:手动传播事务(需谨慎) ```java @Async public void asyncMethod() { TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); transactionTemplate.execute(status -> { transactionalOperation(); // 在新线程中手动开启事务 return null; }); } @Transactional private void transactionalOperation() { /* 数据库操作 */ } ``` --- ### 三、关键注意事项 1. **事务传播行为** 异步方法内的事务总是`PROPAGATION_REQUIRES_NEW`(独立事务),无法参与外部事务[^5]。 2. **注解顺序** 若方法同时使用两个注解,`@Async`必须在`@Transactional`外层: ```java @Async // 正确:异步代理包裹事务代理 @Transactional public void method() { ... } ``` 3. **事务管理器配置** 需为异步任务配置专用事务管理器: ```yaml spring: task: execution: pool: core-size: 5 ``` --- ### 四、验证方法 通过日志检查事务ID: ```java @Async @Transactional public void asyncMethod() { log.info("事务ID: {}", TransactionSynchronizationManager.getCurrentTransactionName()); // 输出应为非null } ``` 若输出`null`,则事务未激活。 --- ### 总结 | 场景 | 事务是否生效 | 原因 | |---------------------|--------------|--------------------| | 同类自调用 | ❌ | 绕过AOP代理 | | 跨Bean调用 | ✅ | 通过代理执行 | | 未配置线程池 | ❌ | 异步执行失败 | | 事务管理器未注入 | ❌ | 缺少事务资源 | > **最佳实践**:将`@Async`和`@Transactional`注解拆分到同Bean中,并通过依赖注入调用[^2][^3]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值