问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

一文带你深入学习Spring注解之@Async使用

创作时间:
作者:
@小白创作中心

一文带你深入学习Spring注解之@Async使用

引用
CSDN
1.
https://m.blog.csdn.net/weixin_51360020/article/details/143991228

在现代Java应用程序中,响应速度是评价程序的重要指标,而异步处理是提高性能和响应速度的重要手段之一。Spring框架提供了@Async注解来简化异步编程,本文将深入讲解@Async注解的使用方法、实现原理以及应用场景。

一、背景

在现代Java应用程序中,响应速度是评价程序的重要指标,而异步处理是提高性能和响应速度的重要手段之一。在如今的Spring Boot全家桶中,它提供了@Async注解来简化异步编程,提高了程序处理数据的速度。

二、基本原理

要想使用@Async注解来执行异步方法,我们需要掌握两个核心注解的原理:@EnableAsync和@Async注解。接下来,就带大家去依次分析其源码,弄清楚如何异步执行。

2.1 @EnableAsync注解

  • 作用:启用 Spring Async功能
  • 使用位置:通常标注在配置类上(@Configuration或 Spring Boot 的主启动类上)。
  • 工作原理:

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

(6)在这个创建增强方法的的函数中,可以看到创建了一个拦截器,而在这个创建切入点的函数中,可以看出方法入参就是Async类型,因此,最终通过这个拦截器来拦截标注有@Async注解的方法。
(7)进入到AnnotationAsyncExecutionInterceptor拦截器,可以看到其继承了AsyncExecutionInterceptor,主要的拦截方法就在这个AsyncExecutionInterceptor父类的invoke()方法中。主要步骤也就下面三步:1. 获取线程池;2. 创建线程;3. 提交任务到线程去执行。
(8)看下获取线程池的处理逻辑。主要步骤是:1.先从缓存中去查找,如果缓存中存在线程池就直接返回这个线程池去使用;2.如果缓存中没找到再根据方法的@Async注解value值进行判断;3.方法的的@Async注解设置了value值,则把value的值当做线程池的bean名称去Spring容器中找到这个bean并返回去使用;4.如果方法的@Async注解没有设置了value值,则使用默认的线程池去调用。
(9)看下获取注解值的方法。主要逻辑就是先拿到方法的Async注解,再根据拿到的注解获取该注解的value属性值。

(10)再看下使用默认线程池的方法。主要是先调用父类的getDefaultExecutor方法去获取线程池对象,如果能获取到就直接使用该线程池对象,如果获取不到就新建个SimpleAsyncTaskExecutor线程池对象。

感兴趣的同学点进到SimpleAsyncTaskExecutor源码里看,就能发现,这个线程池非常简单,通过这个先吃创建的线程不能重复使用,且每次调用都会去创建一个线程,在高并发或者访问很频发按的场景并不适用,很容易造成系统资源耗尽,严重就可能导致系统崩溃。所以在使用@Async注解去实现方法的异步调用时最好使用自定义线程池,具体的使用方法在下文中还会介绍。

2.2 @Async注解

  • 作用:标记方法为异步方法,指定异步允许策略,让对应的方法在执行的时候会按照配置的线程池去执行,不会占用和阻塞主线程。
  • 使用位置:通常标注在在需要支持异步的类或方法上。
  • 工作原理:

能够看出,该注解的作用位置可以是类上也可以是方法上。当标记到类上的时候,该类中所有保留的方法都可以走异步,标记在方法时,仅对该方法生效。通过本章第一小节@EnableAsync注解的实现原理介绍,不难理解实现异步方法的核心其实是@EnableAsync注解,而@Async注解有两个作用,第一个就是实现了切入点的作用,用于标识哪些方法或者类需要异步处理,第二个就是标识自定义线程池。

三、使用方法

1.一定要自定义线程池,不推荐使用默认线程池;
2.这是基于注解来实现的,所以需要在配置类上添加@EnableAsync注解,启用异步功能@Async才能生效;
3.@Async所在的类必须由Spring管理(类需包含@Compent、@Service等Spring注解)
4.调用该异步方法的类也必须是Spring管理的类;
5.异步方法对应类的实例必须也由Spring创建的(@Autowired、@Resource)

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(7);
        executor.setMaxPoolSize(42);
        executor.setQueueCapacity(11);
        executor.setThreadNamePrefix("ht-core-executor-");
        executor.initialize();
        return executor;
    }
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new SimpleAsyncUncaughtExceptionHandler();
    }
}

四、避免踩坑的点

4.1 @EnableAsync注解和Executor的bean不能少

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
@Configuration
@EnableAsync
public class AppConfig{
    // 配置内容
}

4.2 不能和@Transaction注解同时标记,会导致无法异步执行

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class MyService {
    @Transactional
    public void transactionalMethod() {
        // 事务逻辑
        asyncMethod();
    }
    @Async
    public void asyncMethod() {
        // 异步逻辑
    }
}

4.3 被@Async注解修饰的方法,返回值只能是void或者Future以及子类

import java.util.concurrent.CompletableFuture;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class MyAsyncService {
    @Async
    public CompletableFuture<String> asyncMethod() {
        return CompletableFuture.supplyAsync(() -> {
            // 异步任务逻辑
            return "异步任务完成";
        });
    }
}

4.4 @Async注解不能标记在静态方法上

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class AsyncService {
    @Async
    public void asyncMethod() {
        // 异步任务逻辑
    }
    // 错误用法,不能在静态方法上使用@Async
    @Async
    public static void staticAsyncMethod() {
        // 异步任务逻辑
    }
}

4.5 异步方法的类内部不能直接调用该方法this.asyncMethod()

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class AsyncService {
    @Autowired
    private AsyncService self;// 注入自身
    public void externalMethod() {
        self.asyncMethod(); // 异步任务逻辑
    }
    @Async
    public void asyncMethod() {
        // 异步任务逻辑
    }
}

4.6 @Async异常处理

当使用@Async注解时,如果异步方法抛出异常,该异常不会在调用该异步方法的地方直接被捕获,而是会被包装在Future或CompletableFuture对象中。具体来说:

  • 如果返回值是void,异常会被记录在日志中,并且不会返回给调用者。
  • 如果返回值是Future或CompletableFuture,可以通过这些对象来检查和处理异常。
@Service
public class AsyncService {
    @Async
    public Future<String> asyncMethod() {
        return CompletableFuture.supplyAsync(() -> {
            if (true) { // 模拟异常
                throw new RuntimeException("异步任务异常");
            }
            return "异步任务完成";
        });
    }
}
@RestController
public class AsyncController {
    @Autowired
    private AsyncService asyncService;
    @GetMapping("/async")
    public String async() {
        Future<String> future = asyncService.asyncMethod();
        try {
            return future.get(); // 捕获处理异常
        } catch (InterruptedException | ExecutionException e) {
            return "任务失败:" + e.getMessage();
        }
    }
}

五、应用场景

@Async注解适用于各种需要异步处理的场景,例如:
1.后台任务处理
在Web应用中,有些任务(如发送邮件、生成报告)耗时较长,可以使用@Async异步处理,使用户无需等待任务完成即可获得响应。
2.并行处理
对于可以并行处理的任务,如并行数据处理、并行调用多个外部服务,使用@Async可以提高效率。
3.提高系统吞吐量
通过异步调用,可以充分利用多线程资源,提高系统的吞吐量和响应速度。

六、局限性分析

1.线程池管理复杂性:
默认的线程池配置可能无法满足高并发和大规模的生产环境的需求,需要细致地对线程参数进行调优,如核心线程数、最大线程数、队列容量等。这些配置不可能会导致线程资源耗尽或任务堆积,影响服务的稳定性。
2. 缺乏高级功能:
@Async提供的功能相对简单,缺乏如任务调度、任务依赖管理、失败重试、超时控制等高级功能。这些功能在复杂业务场景中可能是必须的,而@Async无法提供。
3.调试和错误处理困难:
异步任务的调试和错误处理较为复杂。异常不会直接传递到调用者,需要通过回调或其他机制处理,增加了代码复杂度和维护难度。
4.可观察性和监控不足:
对于生产系统,监控和可观察性非常重要,@Async在这方面的支持有限,难以监控异步任务的执行情况、性能指标、异常等信息。
5.事务支持问题:
异步方法中的事务管理可能会出现问题。默认情况下,事务上下文不会传递到异步方法中,需要额外配置或编码来确保事务的正确传播。
6.可扩展性限制:
对于需要高度可拓展性的系统,@Async的简单模型可能不足以应对复杂的负载均衡、动态拓展等需求。

七、结语

Spring Boot的@Async注解提供了简单且强大的方式来实现异步处理。通过启用异步支持、定义异步方法并自定义TaskExecutor,可以高效地处理各种异步任务。掌握@Async注解的使用和原理,有助于提升应用程序的性能和响应速度。但是其局限性也是非常明显的,因此,要想用好该@Async注解,还是需要在此基础上加以扩展。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号