为什么不建议直接使用@Async

@Async如果在不设置线程池的情况下,spring会创建SimpleAsyncTaskExecutor的线程池,该线程池每次执行都会创建线程,高并发或者访问很频繁的场景,很容易造成系统线程资源耗尽。

如下文章能够深入了解@Async的机制

一、@Async 简介

从Spring3开始提供了@Async注解,被该注解标注的方法,Spring底层会新建一个线程池或者使用已有的线程池中的线程去异步的执行被标注的方法。

二、@Async 工作原理

@Async与**@Transactional **工作原理基本是一样的,也是通过Spring AOP动态代理去实现的。Spring容器启动初始化bean时,判断类中是否使用了@Async注解,如果使用了则为其创建代理对象,在线程调用@Async注解标注的方法时,通过代理对象执行切入点处理器invoke方法,将方法的执行提交给线程池中的另外一个线程来处理,从而实现了异步执行。

三、在SpringBoot中的使用

@Async在SpringBoot中的使用还是挺简单的,只需要下面两部操作就可以实现方法的异步调用。

  1. 在启动类上使用@EnableAsync注解
@SpringBootApplication
@EnableAsync
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
  1. 将需要异步执行的方法所在的类注入到spring容器,并在异步的方法上添加
@Async注解
@Component
public class AsyncTest {

    /**
 	*  无返回值调用
 	*/
    @Async
    public void asyncDoSomething(){
        // 具体业务逻辑处理
        ……
    }

    /**
 	* 有返回值的调用,使用Future来接收异步任务执行的结果
 	* 对于返回值是Future,需要我们在方法中捕获异常并处理,或者在调用方在调用Futrue.get时捕获异常进行处理
 	* 
 	* @return
 	*/
    @Async
    public Future<String> asyncDoSomethingToReturn() {
        Future<String> future;
        try {
            // 具体业务逻辑处理
            ……
            future = new AsyncResult<String>("success");
        } catch (InterruptedException e) {
            future = new AsyncResult<String>("InterruptedException");
        } catch(IllegalArgumentException e){
            future = new AsyncResult<String>("IllegalArgumentException");
        }
        return future;
    }
}

四、@Async失效场景

  1. 非公有方法:注解标注方法修饰符为非public时,异步任务会失效。因为@Async是基于动态代理实现的,如果被@Async标注的方法的修饰符不是public的话,将不会对该bean进行代理对象创建或者不会对方法进行代理调用,从而也就无法将任务交由现场池中的某个线程去异步执行任务。
  2. final 修饰或者static修饰的方法:如果动态代理使用的是cglib实现的话,由于cglib实现动态代理的方式主要是靠运行期动态创建目标类的子类,从而无法创建代理对象,也就无法将任务交由现场池中的某个线程去异步执行任务。
  3. 类内部访问:在同一个类中调用被@Async方法调用时,也会因为在当前类中无法生成的代理对象来调用,只能调用实际的目标方法,也就无法将任务交由现场池中的某个线程去异步执行任务。
  4. spring无法扫描到异步类,没加注解@Async 或 @EnableAsync注解
  5. 注解@Async的返回值只能为void或者Future
  6. 在Async 方法上标注@Transactional是没用的,但在Async 方法调用的方法上标注@Transactional 是有效的

五、@Async 底层源码解析

5.1 @Async注解源码

image.png
从上面@Async注解源码可以看出,只有一个value属性,这个属性就是线程池Bean的名称,如果在注解中设置了这个值,则根据这个值从Spring容器中获取这个线程池的Bean去异步执行方法;如果没设置,则会使用默认的线程池去异步执行该方法。所以这里想要指定具体的线程池去异步执行方法的话,只需要在@Async(“taskExecutor”)中设置线程池的bean的名称当作注解的value属性值就好。

5.2 @EnableAsync注解源码

image.png
(1)根据上面@EnableAsync源码可以看出,该注解中使用了一个@Import注解导入了一个AsyncConfigurationSelector.class类。
image.png
(2)进入到AsyncConfigurationSelector类,可以看到该类继承了AdviceModeImportSelector抽象类,AdviceModeImportSelector抽象类又实现了ImportSelector接口。并且在selectImports()方法中根据不同的AdviceMode来选择和返回适当的导入语句。
image.png
(3)进入到ProxyAsyncConfiguration配置类中,可以看到会创建一个AsyncAnnotationBeanPostProcessor对象,并设置相关的属性后返回该对象。
image.png
(4)再进入到AsyncAnnotationBeanPostProcessor类中,有个setBeanFactory()方法,在这个方法中往BeanFactory容器中增加了一个AsyncAnnotationAdvisor增强器。
image.png
(5)进入这个AsyncAnnotationAdvisor增强器的构造函数可以看到,分别创建了一个通知以及切入点。
image.png
(6)在这个创建增强方法的的函数中,可以看到创建了一个拦截器,主要通过这个拦截器来拦截标注有@Async注解的方法。
image.png
(7)进入到AnnotationAsyncExecutionInterceptor拦截器,可以看到其继承了AsyncExecutionInterceptor,主要的拦截方法就在这个AsyncExecutionInterceptor父类的invoke()方法中。主要步骤也就下面三步:

  1. 获取线程池;
  2. 创建Callbale任务;
  3. 提交任务到线程去执行。
    image.png
    (8)看下获取线程池的处理逻辑。主要步骤是:
    1.先从缓存中去查找,如果缓存中存在线程池就直接返回这个线程池去使用;
    2.从@Async注解设置了value值,则把value的值当做线程池的bean名称去Spring容器中找到这个bean并返回去使用;
    3.如果方法的@Async注解没有设置了value值,则使用默认的线程池去调用。
    image.png

(9)看下获取注解值的方法。主要逻辑就是先拿到方法的Async注解,再根据拿到的注解获取该注解的value属性值。
image.png
(10)再看下使用默认线程池的方法。主要是先调用父类的getDefaultExecutor方法去获取线程池对象,如果能获取到就直接使用该线程池对象,如果获取不到就新建个SimpleAsyncTaskExecutor线程池对象。
image.png
image.png
进入SimpleAsyncTaskExecutor的execute方法去创建线程执行任务。
image.png
在doExecute()方法中每次都会创建一个线程去执行。
image.png

由于SimpleAsyncTaskExecutor是个简单的线程池,通过这个线程池创建的线程不能重复使用,且每次调用都会去创建一个线程,在高并发或者访问很频繁的场景并不适用,很容易造成系统线程资源耗尽,严重可能导致系统崩溃。所以在使用@Async注解去实现方法的异步调用时最好使用自定义线程池或者ThreadPoolTaskExecutor线程池去替换掉@Async注解默认的线程池。

六、注意事项

不建议直接使用@Async,上面源码中已经分析了,如果直接使用,一般会使用到Spring创建的SimpleAsyncTaskExecutor的线程池,每次执行都会创建线程,高并发或者访问很频繁的场景,很容易造成系统线程资源耗尽。所以在调用时,使用自定义线程池。

@Configuration
@EnableAsync
public class AsyncExecutorConfig {

    @Bean("successExecutor")
    public Executor executor() {
        // hutool工具
        ThreadFactory threadFactory = new ThreadFactoryBuilder().setNamePrefix("registerSuccessExecutor-").build();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(100, 200, 0L, TimeUnit.MICROSECONDS,
                new LinkedBlockingDeque<>(1024), threadFactory, new ThreadPoolExecutor.AbortPolicy());
        return threadPoolExecutor;
    }
}


在使用@Async使用自定义线程池

@Service
public class AsyncTest {

  
    @Async("successExecutor")
    public void asyncDoSomething() {
        // 具体业务逻辑处理
        ……
    }
  }
<think>好的,我现在需要帮助用户解决在使用Spring框架的@Async注解时出现的创建错误问题。首先,我得回忆一下常见的@Async相关问题以及解决方法。根据用户提供的引用,特别是引用[3]提到测试异步服务的工具,可能涉及到异步配置是否正确的问题。 首先,检查用户是否启用了异步支持。在Spring中,使用@Async需要在配置类上添加@EnableAsync,否则注解会生效。用户可能漏掉了这个注解,或者配置类没有被正确扫描到。这时候应该提示用户检查配置类是否正确添加了该注解,并确保配置类所在的包被组件扫描覆盖。 接下来,考虑线程池的配置。如果没有自定义线程池,Spring使用默认的SimpleAsyncTaskExecutor,这可能适合生产环境,而且可能出现资源耗尽的问题。用户需要配置自定义的线程池,比如通过实现AsyncConfigurer接口或定义TaskExecutor bean。引用[3]中的工具如Awaitility可能帮助测试异步行为,但正确配置线程池是关键。 然后是方法可见性的问题。@Async注解的方法必须是public的,否则代理无法拦截调用,导致异步失效。用户可能将方法定义成了private或其他非public类型,需要检查并修改方法的访问修饰符。 另外,自调用问题也是一个常见错误。如果一个类内部的方法A调用同一个类的另一个@Async方法B,由于代理机制的限制,异步调用会生效。用户需要将方法B移到另一个Bean中,或者通过AopContext.currentProxy()来调用,但这可能增加代码复杂度。 异常处理也能忽视。异步方法抛出的异常会被调用方捕获,用户需要实现AsyncUncaughtExceptionHandler来处理未捕获的异常。引用[4]提到AspectJ的使用,可能涉及到AOP配置,但这里更直接的是异常处理机制的设置。 最后,建议用户检查依赖和版本兼容性。Spring Boot的自动配置可能简化了部分设置,但版本兼容可能导致意外错误。确保Spring核心和异步相关库的版本一致,并查看官方文档是否有变更说明。 综合以上几点,应该分步骤引导用户检查配置、线程池、方法可见性、自调用和异常处理,并提供代码示例和可能的解决方案。同时,根据用户提供的引用,特别是引用[3]中的测试工具,可以推荐使用Awaitility等工具来验证异步行为是否正确,确保问题得到解决。</think>### 解决@Async注解的创建错误问题 在使用Spring框架的@Async注解实现异步编程时,常见的错误通常与配置、线程池设置或方法可见性相关。以下是逐步排查和解决方案: --- #### 1. **检查是否启用异步支持** - **问题**:未在配置类中添加`@EnableAsync`,导致异步注解失效。 - **解决**:在Spring配置类中添加注解: ```java @Configuration @EnableAsync public class AsyncConfig {} ``` - **验证**:确保配置类被组件扫描覆盖。 --- #### 2. **配置自定义线程池** - **问题**:默认的`SimpleAsyncTaskExecutor`适用于生产环境,可能导致资源耗尽或性能问题[^3]。 - **解决**:通过`AsyncConfigurer`自定义线程池: ```java @Configuration @EnableAsync public class AsyncConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(100); executor.initialize(); return executor; } } ``` --- #### 3. **检查方法可见性** - **问题**:`@Async`注解的方法必须是`public`,否则代理无法拦截调用。 - **解决**:确保方法为`public`: ```java @Service public class MyService { @Async public void asyncMethod() { // 异步逻辑 } } ``` --- #### 4. **避免自调用(Self-Invocation)** - **问题**:同一类中的方法A调用方法B(标注`@Async`),由于代理机制限制,异步生效。 - **解决**: - 将异步方法移到另一个Bean中。 - 使用`AopContext.currentProxy()`调用(需启用`@EnableAspectJAutoProxy`): ```java ((MyService) AopContext.currentProxy()).asyncMethod(); ``` --- #### 5. **处理异步异常** - **问题**:异步方法抛出的异常默认会传播到调用方。 - **解决**:实现`AsyncUncaughtExceptionHandler`: ```java @Configuration @EnableAsync public class AsyncConfig implements AsyncConfigurer { @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return (ex, method, params) -> { System.err.println("异步方法异常: " + method.getName()); ex.printStackTrace(); }; } } ``` --- #### 6. **验证依赖和版本兼容性** - **问题**:Spring Boot版本与异步库兼容。 - **解决**: - 检查`pom.xml`或`build.gradle`中的依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-async</artifactId> </dependency> ``` - 参考Spring官方文档确认版本兼容性。 --- ### 示例代码 ```java @Service public class NotificationService { @Async public void sendAsyncNotification() { // 模拟耗时操作 try { Thread.sleep(3000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println("异步通知发送完成"); } } // 调用方 @RestController public class NotificationController { @Autowired private NotificationService notificationService; @GetMapping("/send") public String sendNotification() { notificationService.sendAsyncNotification(); return "请求已接收,处理中..."; } } ``` --- ### 相关问题 1. **如何测试Spring异步方法的执行结果?** - 可使用`Awaitility`库等待异步操作完成并验证结果。 2. **Spring异步编程中如何实现超时控制?** - 结合`@Async`与`@Timeout`注解,或在`Future`返回值中设置超时逻辑。 3. **如何监控Spring异步任务的执行状态?** - 通过`ThreadPoolTaskExecutor`的`getActiveCount()`或集成Micrometer进行指标采集。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值