文章目录
- 前言
- 一、配置类未启用异步支持
- 二、线程池未正确配置
- 三、Spring代理未生效
- 四、方法不是 public 的
- 五、调用者与被调用者在同一个类中
- 六、异常处理不当
- 七、使用 @Transactional 与 @Async 同时注解方法,导致事务失效
- 总结
前言
在Spring Boot中,@Async注解就像一把瑞士军刀,能帮你轻松处理那些耗时的任务,让主线程可以继续忙别的事儿。再强大的工具,如果使用不好依然会出现问题。为了避免这些坑,咱们得深入了解下@Async是怎么工作的,还要知道怎么用才能不出问题。
一、配置类未启用异步支持
如果配置类中没有启用异步支持,即没有使用 @EnableAsync 注解,那么 @Async 注解同样不会生效。
// 没有使用 @EnableAsync 注解,因此不会启用异步支持
@Configuration
public class AsyncConfig {
// ... 其他配置 ...
}
@Service
public class MyService {
@Async
public void asyncMethod() {
// 模拟耗时操作
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Async method executed.");
}
}
解决方案:
@Configuration
@EnableAsync
public class AsyncConfig {
// ... 其他配置 ...
}
二、线程池未正确配置
在使用 @Async 注解时,如果没有正确配置线程池,可能会遇到异步任务没有按预期执行的情况。
比如:线程池被配置为只有一个线程,且该线程一直被占用,那么新的异步任务就无法执行。
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
// 创建一个只有一个线程的线程池,这会导致并发问题
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(1);
executor.setMaxPoolSize(1);
executor.setQueueCapacity(10);
executor.setThreadNamePrefix("Async-");
executor.initialize();
return executor;
}
// ... 其他配置 ...
}
@Service
public class MyService {
@Async
public void asyncMethod() {
// 模拟耗时操作
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Async method executed.");
}
}
解决方案:
正确配置线程池:确保线程池配置合理,能够处理预期的并发任务量
三、Spring代理未生效
如果通过 new 关键字直接创建了服务类的实例,而不是通过 Spring 容器来获取,那么 Spring 的 AOP 代理将不会生效,导致 @Async 注解无效。
@Service
public class MyService {
@Async
public void asyncMethod() {
// 模拟耗时操作
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Async method executed.");
}
}
public class SomeNonSpringClass {
public void someMethod() {
MyService myService = new MyService(); // 直接通过 new 创建 MyService 实例,不会经过 Spring 代理
myService.asyncMethod(); // 这里 @Async 不会生效
}
}
解决方案:
合理利用依赖注入:始终通过 Spring 容器来获取服务类的实例,而不是直接通过 new 关键字创建
@Service
public class MyService {
@Async
public void asyncMethod() {
// 模拟耗时操作
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Async method executed.");
}
}
@Service
public class SomeNonSpringClass {
@Autowired
private MyService myService;
public void someMethod() {
myService.asyncMethod(); // 这里 @Async 会生效
}
}
四、方法不是 public 的
@Async 注解的方法必须是 public 的,否则不会被 Spring AOP 代理捕获,导致异步执行不生效。
@Service
public class MyService {
@Async // 但这个方法不是 public 的,所以 @Async 不会生效
protected void asyncMethod() {
// 模拟耗时操作
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Async method executed.");
}
public void callAsyncMethod() {
asyncMethod(); // 直接调用,但由于 asyncMethod 不是 public 的,因此不会异步执行
}
}
解决方案:
确保异步方法是 public 的
五、调用者与被调用者在同一个类中
当调用 @Async 注解的方法的类和被调用的方法在同一个类中时,@Async 注解不会生效。
因为 Spring 的 AOP 代理是基于接口的,对于同一个类中的方法调用,不会经过代理,因此 @Async 注解不会被处理。
@Service
public class MyService {
@Async
public void asyncMethod() {
// 模拟耗时操作
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Async method executed.");
}
public void callAsyncMethod() {
asyncMethod(); // 直接调用,不会异步执行
}
}
解决方案:
- 确保异步方法和调用它的方法不在同一个类中。可以将异步方法提取到一个单独的 Service 中,并在需要的地方注入这个 Service。
- 确保异步方法的执行类(即包含 @Async 注解方法的类)被 Spring 容器管理,比如通过 @Service、@Component 等注解标注
//一定使用@Service、@Component 等注解标注,确保执行类被Spring管理,因为异步线程是通过动态代理实现的
@Service
public class AsyncService {
@Async
public void asyncMethod() {
// 模拟耗时操作
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Async method executed.");
}
}
六、异常处理不当
如果在异步方法中抛出了异常,并且没有妥善处理,那么这个异常可能会导致任务失败,而调用者可能无法感知到异常的发生。
@Service
public class MyService {
@Async
public void asyncMethod() {
// 模拟一个可能会抛出异常的耗时操作
throw new RuntimeException("Async method exception");
}
}
// 调用者
@Service
public class CallerService {
@Autowired
private MyService myService;
public void callAsyncMethod() {
myService.asyncMethod(); // 调用异步方法,但如果该方法抛出异常,调用者不会立即感知到
}
}
解决方案:
合理处理异常:在异步方法中妥善处理异常,可以通过 Future 对象来捕获异步任务执行过程中抛出的异常。
七、使用 @Transactional 与 @Async 同时注解方法,导致事务失效
在同一个方法上同时使用 @Transactional 和 @Async 注解可能会导致问题。
由于 @Async 会导致方法在一个新的线程中执行,而 @Transactional 通常需要在一个由 Spring 管理的事务代理中执行,这两个注解的结合使用可能会导致事务管理失效或行为不可预测。
此种场景不会导致@Async注解失效,但是会导致@Transactional注解失效,也就是事务失效。
@Service
public class MyService {
@Autowired
private MyRepository myRepository;
// 错误的用法:同时使用了 @Transactional 和 @Async
@Transactional
@Async
public void asyncTransactionalMethod() {
// 模拟一个数据库操作
myRepository.save(new MyEntity());
// 模拟可能抛出异常的代码
if (true) {
throw new RuntimeException("Database operation failed!");
}
}
}
@Repository
public interface MyRepository extends JpaRepository<MyEntity, Long> {
// ...
}
@Entity
public class MyEntity {
// ... 实体类的属性和映射 ...
}
上面的代码,在抛出异常的时候,我们期望的是回滚前面的数据库保存操作,但是因为事务失效,会导致错误数据成功保存进数据库。
解决方案:
正确配置事务,比如单独提取事务执行的逻辑到一个新的Service里,事务执行方法单独使用@Transactional标识
@Service
public class MyService {
@Autowired
private MyTransactionalService myTransactionalService;
@Autowired
private AsyncExecutor asyncExecutor;
public void callAsyncTransactionalMethod() {
// 在事务中执行数据库操作
MyEntity entity = myTransactionalService.transactionalMethod();
// 异步执行其他操作
asyncExecutor.execute(() -> {
// 这里执行不需要事务管理的异步操作
// ...
});
}
}
@Service
public class MyTransactionalService {
@Autowired
private MyRepository myRepository;
@Transactional
public MyEntity transactionalMethod() {
// 在事务中执行数据库操作
return myRepository.save(new MyEntity());
}
}
@Component
public class AsyncExecutor {
@Async
public void execute(Runnable task) {
task.run();
}
}
总结
这里面,绝大多数人会遇到的坑点主要会集中在没有配置自定义线程池、异步方法在同一个类中调用、事务不起作用这几个问题上。 所以,推荐还是专门定义一个AsyncService,将异步方法都写在里面,需要使用的时候,就在其他类将其注入即可。
“笑对人生,智慧同行!博客新文出炉,微信订阅号更新更实时,等你笑纳~”