springboot整合异步任务——@Async注解

迷南。 2024-04-03 12:15 211阅读 0赞

目录

1.简单介绍

2.springboot开启异步任务

3.实验验证

4.注意事项

1.简单介绍

异步任务:有时候在某个调用中,我们需要调用 A, B, C三个业务流程;如他们都是同步调用,则需要将他们都顺序执行完毕之后,方才算作过程执行完毕,需要A+B+C三个业务一共的执行时间;但如果就我们可以让A、B、C三个业务异步同时执行(前提是三个任务没有先后顺序关系),那么我们就只需要A、B、C三个业务中执行时间最长的那个任务的时间即可执行完毕。这就是异步执行任务。

在Spring中,基于@Async标注的方法,称之为异步方法;这些方法将在执行的时候,将会在独立的线程中被执行,调用者无需等待它的完成,即可继续其他的操作 。

@Async 的原理是通过 Spring AOP动态代理 的方式来实现的。Spring容器启动初始化bean时,判断类中是否使用了@Async 注解:如果使用了,则为其创建切入点和切入点处理器,根据切入点创建代理,在线程调用@Async注解标注的方法时,会调用代理,执行切入点处理器invoke方法,将方法的执行提交给线程池中的另外一个线程来处理,从而实现了异步执行。

2.springboot开启异步任务

首先,我们需要编写自己的线程池,避免spring自身不断地创建线程,导致内存溢出等问题。

自定义线程池的方法有两种,可自行进行选择:

(1)application.xml中配置:

  1. Spring:
  2. task:
  3. execution:
  4. pool:
  5. max-size: 50 #最大线程数:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
  6. core-size: 10 #心线程数:线程池创建时候初始化准备就绪的线程数
  7. queue-capacity: 100 #线程池所使用的缓冲队列
  8. keep-alive: 60 #允许线程的空闲时间:超过了核心线程出之外的线程,在空闲时间到达之后会被释放

此方式还需要在启动类中添加 @EnableAsync 注解,表示开启异步任务。

(2)编写配置类: (建议使用该方法)

  1. import org.springframework.context.annotation.Bean;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.scheduling.annotation.AsyncConfigurer;
  4. import org.springframework.scheduling.annotation.EnableAsync;
  5. import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
  6. @Configuration
  7. @EnableAsync // 启用异步任务
  8. public class AsyncConfiguration implements AsyncConfigurer {
  9. // 声明一个线程池(并指定线程池的名字)
  10. @Bean("AsyncTask")
  11. public ThreadPoolTaskExecutor asyncExecutor() {
  12. //创建线程池
  13. ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
  14. //核心线程数:线程池创建时候初始化准备就绪的线程数,
  15. //作用:等待来接受异步任务,一直存在不会被释放
  16. int corePoolSize = 10;
  17. executor.setCorePoolSize(corePoolSize);
  18. //线程池所使用的缓冲队列
  19. executor.setQueueCapacity(100);
  20. //等待任务在关机时完成--表明等待所有线程执行完
  21. executor.setWaitForTasksToCompleteOnShutdown(true);
  22. //最大线程数:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
  23. //作用:控制资源并发
  24. executor.setMaxPoolSize(corePoolSize*5);
  25. //允许线程的空闲时间:超过了核心线程出之外的线程,在空闲时间到达之后会被释放
  26. executor.setKeepAliveSeconds(30);
  27. //线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
  28. executor.setThreadNamePrefix("Async-");
  29. executor.initialize();
  30. return executor;
  31. }
  32. }

其中 ThreadPoolTaskExecutor类是spring封装的线程池包,其使用方式与java juc包下的ThreadPoolExecutor 类相似。

而关于线程池中线程的分配,流程如下:

以上述代码中线程池为例(10 core,50 max,100 queue),当200个并发同时进来时,首先会占用10个核心线程,然后100个进入缓冲队列,缓冲队列满了后,根据max 再打开新线程40个,剩下50个则会根据丢弃策略进行丢弃。

然后,我们只需要在对应的业务类中的方法是标记 @Async注解即可开启异步任务:

  1. @Override
  2. @Async("AsyncTask")
  3. public void testTask01() {
  4. log.info("任务1开启");
  5. try {
  6. Thread.sleep(2000);
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }
  10. log.info("任务1结束");
  11. }

关于@Async注解注解:

  • 参数为配置类中的线程池名称,不写则为默认线程池
  • 被@Async注解的方法返回值只能是void或者Future<>类,比如Future(返回类型),并在方法中return new AsyncResult<>(返回值);

    1. @Async("AsyncTask")
    2. public Future<String> testTask03() {
    3. log.info("任务3开启");
    4. try {
    5. Thread.sleep(1500);
    6. } catch (InterruptedException e) {
    7. e.printStackTrace();
    8. }
    9. log.info("任务3结束");
    10. return new AsyncResult<>("success");
    11. }

    然后,我们可以通过 get 方法获取值”success”。

3.实验验证

业务类中添加任务2代码:

  1. @Override
  2. @Async("AsyncTask")
  3. public void testTask02() {
  4. log.info("任务2开启");
  5. try {
  6. Thread.sleep(1500);
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }
  10. log.info("任务2结束");
  11. }

在控制类中编写如下代码,进行验证:

  1. @GetMapping("/test")
  2. public void test(){
  3. long start = System.currentTimeMillis();
  4. userAddressService.testTask01();
  5. userAddressService.testTask02();
  6. long end = System.currentTimeMillis();
  7. log.info("future(): 耗时" + (end - start) + "ms");
  8. }

当任务1和任务2不开启异步任务时,顺序执行,效果如下:

e461a58f9534446dad2c7713e12482c0.png

而当任务1和任务2开启异步任务后,效果如下:

6dd1c027b3c54d80b228bf2fc33b7a76.png

可以看到并没有顺序执行,遇到异步任务后新开了一个线程执行,然后直接执行main线程中的代码;任务1和任务2则是同时开始执行,成功异步执行。

值得注意的是,如果controller的方法中设置了返回值,且返回值和异步方法的返回值无关(或无返回值)执行时会不等异步方法执行完,先返回结果至前端。

修改控制类中代码,改为循环执行任务1:

  1. @GetMapping("/test")
  2. public void test(){
  3. long start = System.currentTimeMillis();
  4. for (int i = 0; i < 10; i++) {
  5. userAddressService.testTask01();
  6. }
  7. //userAddressService.testTask02();
  8. long end = System.currentTimeMillis();
  9. log.info("future(): 耗时" + (end - start) + "ms");
  10. }

15a2cc14eba1409291cec5f06a726a50.png 可以看到10个任务同时异步开始执行。

此处需要注意的是同时并发的任务数不要超过缓冲队列数+最大线程数,否则在分配完线程资源后会抛出异常。具体如何设置线程池,还需要根据实际情况考虑。

4.注意事项

如下方式会使@Async注解失效

  • 异步方法使用static修饰
  • 异步类没有使用@Component(或其他注解)进行注册,导致spring无法扫描到异步类
  • 异步方法不能与被调用的异步方法在同一个类或方法中,比如task01方法中,执行task02方法,尽管标注了async注解,也不会异步任务
  • 类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象

发表评论

表情:
评论列表 (有 0 条评论,211人围观)

还没有评论,来说两句吧...

相关阅读