异步注解:@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)

相关推荐
uzong4 小时前
技术故障复盘模版
后端
GetcharZp4 小时前
基于 Dify + 通义千问的多模态大模型 搭建发票识别 Agent
后端·llm·agent
桦说编程5 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
IT毕设实战小研5 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
wyiyiyi5 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
阿华的代码王国6 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
Jimmy6 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
AntBlack7 小时前
不当韭菜V1.1 :增强能力 ,辅助构建自己的交易规则
后端·python·pyqt
bobz9657 小时前
pip install 已经不再安全
后端
寻月隐君8 小时前
硬核实战:从零到一,用 Rust 和 Axum 构建高性能聊天服务后端
后端·rust·github