异步注解:@Async

@Async的基本使用

  1. 配置类上添加@EnableAsync注解
  2. 需要异步执行的方法的所在类由Spring管理
  3. 需要异步执行的方法上添加了@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()方法。

解决方案

  1. 确保异步方法和调用它的方法不在同一个类中。可以将异步方法提取到一个单独的 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.");  
    }  
}

解决方案

  1. 在配置类上使用 @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");
    }
}

在调��异步方法时,可以通过Futureget()方法捕获到异常。

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)

@Async异步失效的9种场景-腾讯云开发者社区-腾讯云 (tencent.com)

深入了解Spring Boot中@Async注解的8大坑点-腾讯云开发者社区-腾讯云 (tencent.com)

相关推荐
程序员阿龙1 小时前
SpringBoot智慧旅游在线平台的设计与实现(源码+论文+部署讲解等)
spring boot·后端·旅游
不太会写1 小时前
基于若依框架开发的Spring Boot+Vue的MES(生产制造执行系统)是一种专为中小型工厂设计的ERP(企业资源计划)系统
vue.js·spring boot·后端
小洪爱分享1 小时前
Bug 解决 | 后端项目无法正常启动,或依赖服务连接失败
java·经验分享·redis·后端·mysql·bug·maven
Tony Bai1 小时前
使用TLA+形式化验证Go并发程序
大数据·开发语言·人工智能·后端·golang
Rust语言中文社区2 小时前
【Rust日报】终端表格查看工具
开发语言·后端·rust
小奏技术3 小时前
为什么golang不支持可重入锁
后端·go
赛博末影猫3 小时前
SpringCloud入门简介
后端·spring·spring cloud
程序研4 小时前
JAVA中的内部比较器Comparable
java·开发语言·后端·comparable
weixin_lizhao4 小时前
Dubbo源码深度解析(三)
java·网络·后端·spring·dubbo
浪淘沙jkp4 小时前
智慧水务项目(二)django(drf)+angular 18 创建通用model,并对orm常用字段进行说明
后端·python·django