@Async的基本使用
- 配置类上添加
@EnableAsync
注解 - 需要异步执行的方法的所在类由Spring管理
- 需要异步执行的方法上添加了
@Async
注解
异步注解失效的场景
场景一:内部方法调用
当调用 @Async注解
的方法的类和被调用的方法在同一个类中时,@Async 注解不会生效。因为 Spring 的 AOP 代理是基于接口的,对于同一个类中的方法调用,不会经过代理,因此 @Async 注解不会被处理。例如:
typescript
@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(); // 直接调用,不会异步执行
}
}
而在类中直接进行方法的内部调用,在callAsyncMethod()方法中调用callAsyncMethod()方法,调用的是该类原对象的callAsyncMethod方法,相当于调用了this.callAsyncMethod()方法,而并非MyService代理类的callAsyncMethod()方法。
解决方案
- 确保异步方法和调用它的方法不在同一个类中。可以将异步方法提取到一个单独的 Service 中,并在需要的地方注入这个 Service。
场景二:配置类未启用异步支持
如果配置类中没有启用异步支持,即没有使用 @EnableAsync 注解,那么 @Async 注解同样不会生效。
less
// 没有使用 @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.");
}
}
解决方案
- 在配置类上使用 @EnableAsync 注解,启用异步支持。
场景三:方法不是 public 的
typescript
@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 的,因此不会异步执行
}
}
因为private修饰的方法,只能在MyService类的对象中使用
解决方案 确保异步方法是 public 的
场景四:线程池未正确配置
在使用 @Async 注解时,如果没有正确配置线程池,可能会遇到异步任务没有按预期执行的情况。例如,线程池被配置为只有一个线程,且该线程一直被占用,那么新的异步任务就无法执行。
typescript
@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.");
}
}
解决方案
正确配置线程池:确保线程池配置合理,能够处理预期的并发任务量
场景五:异常处理不当
如果在异步方法中抛出了异常,并且没有妥善处理,那么这个异常可能会导致任务失败,而调用者可能无法感知到异常的发生。
typescript
@Service
public class YourService {
@Async
public void asyncMethod() {
// 异步执行的逻辑
throw new RuntimeException("Async method exception");
}
}
解决方案
解决方案是将返回值设置为Future
,这样就可以在调用get()
方法时捕获到异常。、
typescript
@Service
public class YourService {
@Async
public Future<Void> asyncMethod() {
// 异步执行的逻辑
throw new RuntimeException("Async method exception");
}
}
在调��异步方法时,可以通过Future
的get()
方法捕获到异常。
typescript
@Service
public class YourService {
@Autowired
private YourService self;
public void callAsyncMethod() {
try {
self.asyncMethod().get();
} catch (Exception e) {
// 捕获异常
}
}
}
场景六:自己new的对象
在项目中,我们经常需要new一个对象,然后对他赋值,或者调用它的方法。
但如果new了一个Service类的对象,可能会出现一些意想不到的问题,例如:
typescript
@Service
public class UserService {
@Async
public void async(String value) {
log.info("async:{}", value);
}
}
@Service
public class TestService {
public void test() {
UserService userService = new UserService();
userService.async("test");
}
}
我们自己new的对象,不会被Spring管理
场景七:使用 @Transactional 与 @Async 同时注解方法,导致事务失效
在同一个方法上同时使用 @Transactional 和 @Async 注解可能会导致问题。由于 @Async 会导致方法在一个新的线程中执行,而 @Transactional 通常需要在一个由 Spring 管理的事务代理中执行,这两个注解的结合使用可能会导致事务管理失效或行为不可预测。此种场景不会导致@Async注解失效,但是会导致@Transactional注解失效,也就是事务失效。例如:
less
@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 {
// ... 实体类的属性和映射 ...
}
上面的代码,在抛出异常的时候,我们期望的是回滚前面的数据库保存操作,但是因为事务失效,会导致错误数据成功保存进数据库。
typescript
@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();
}
}
场景八:方法返回值错误
返回值可以是void、int、String、User等等,但如果返回值定义错误,也可能会导致@Async注解的异步功能失效
typescript
public class UserService {
@Async
public String async(String value) {
log.info("async:{}", value);
return value;
}
}
UserService类的async方法的返回值是String,这种情况竟然会导致@Async注解的异步功能失效。
在AsyncExecutionInterceptor类的invoke()方法,会调用它的父类AsyncExecutionAspectSupport中的doSubmit方法,该方法时异步功能的核心代码,如下:
kotlin
protected Object doSubmit(Callable<Object> task, AsyncTaskExecutor executor, Class<?> returnType) {
if (completableFuturePresent) {
Future<Object> result = CompletableFutureDelegate.processCompletableFuture(returnType, task, executor);
if (result != null) {
return result;
}
}
if (ListenableFuture.class.isAssignableFrom(returnType)) {
return ((AsyncListenableTaskExecutor) executor).submitListenable(task);
}
else if (Future.class.isAssignableFrom(returnType)) {
return executor.submit(task);
}
else {
executor.submit(task);
return null;
}
}
chatgpt的解释:
这个函数用于根据所选的执行器执行给定的任务,并返回执行结果。根据返回类型的不同,可能会返回对应的Future句柄。如果returnType是CompletableFuture相关类型,则使用CompletableFutureDelegate.processCompletableFuture方法处理并返回结果。如果returnType是ListenableFuture类型,则使用AsyncListenableTaskExecutor执行并返回结果。如果returnType是Future类型,则直接使用executor执行并返回结果。如果returnType既不是CompletableFuture相关类型,也不是ListenableFuture类型,也不是Future类型,则只执行任务,不返回结果。
省流 :相关方法的返回值必须是void
或者Future
相关的类:CompletableFuture。
场景九:方法用static修饰了
typescript
@Service
public class UserService {
@Async
public static void async(String value) {
log.info("async:{}", value);
}
}
这时@Async的异步功能会失效,因为这种情况idea会直接报错:Methods annotated with '@Async' must be overridable 。
使用@Async注解声明的方法,必须是能被重写的,很显然static修饰的方法,是类的静态方法,是不允许被重写的。
因此这种情况下,@Async注解的异步功能会失效。
场景十:方法用final修饰
这种情况下idea也会直接报错:Methods annotated with '@Async' must be overridable 。
因为使用final关键字修饰的方法,是没法被子类重写的。
因此这种情况下,@Async注解的异步功能会失效。
@Async实现原理
解析都是从这一步开始的,不要问我为什么,看了几篇博客都是从这里开始
AsyncAnnotationAdvisor的创建过程:
接着看this.advice = buildAdvice(executor, exceptionHandler):
至此,我们终于找到方法拦截器了,为何是它,看看它的继承体系:
验证 :既然找到了方法拦截器,那么我们就打断点在拦截方法里,执行之前的测试代码:拦截方法在它的父类中:AsyncExecutionInterceptor
原理:1.获取线程池 2.创建callable 3.执行线程 重点是线程池的获取逻辑 :
由上所得:线程池会首先通过用户自己配置的为准:
所以,我们可以自定义线程池,然后注入spring中,将bean的名字放到@Async注解的value值即可
最后我们看看默认的线程池是怎么获取的: 1.先判断是否存在线程池
2.如果为空,则上锁,给map这个对象上锁
3.调用getDefaultExecutor方法
getDefaultExecutor方法
由此可见,会先获取容器中TaskExecutor的线程池,获取不到,就获取指定beanName的线程池DEFAULT_TASK_EXECUTOR_BEAN_NAME = "taskExecutor",但是根本找不到;
我们看看默认的线程池
总结:@Async的异步线程池获取顺序:
实际开发可能遇到的问题 (老前辈的文章,QAQ): mp.weixin.qq.com/s/SS5fq4OLf...
参考文章: 一文彻底讲透@Async注解的原理和使用方法 - yangxiaohui227 - 博客园 (cnblogs.com)