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

目录

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中配置:

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

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

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

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
@EnableAsync  // 启用异步任务
public class AsyncConfiguration implements AsyncConfigurer {
    // 声明一个线程池(并指定线程池的名字)
    @Bean("AsyncTask")
    public ThreadPoolTaskExecutor asyncExecutor() {
        //创建线程池
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //核心线程数:线程池创建时候初始化准备就绪的线程数,
        //作用:等待来接受异步任务,一直存在不会被释放
        int corePoolSize = 10;
        executor.setCorePoolSize(corePoolSize);

       //线程池所使用的缓冲队列
        executor.setQueueCapacity(100);
        //等待任务在关机时完成--表明等待所有线程执行完
        executor.setWaitForTasksToCompleteOnShutdown(true);

        //最大线程数:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        //作用:控制资源并发
        executor.setMaxPoolSize(corePoolSize*5);

        //允许线程的空闲时间:超过了核心线程出之外的线程,在空闲时间到达之后会被释放
        executor.setKeepAliveSeconds(30);

        //线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
        executor.setThreadNamePrefix("Async-");
        executor.initialize();
        return executor;
    }
}

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

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

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

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

    @Override
    @Async("AsyncTask")
    public void testTask01() {
        log.info("任务1开启");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("任务1结束");
    }

关于@Async注解注解:

  • 参数为配置类中的线程池名称,不写则为默认线程池
  • 被@Async注解的方法返回值只能是void或者Future<>类,比如Future(返回类型),并在方法中return new AsyncResult<>(返回值);
        @Async("AsyncTask")
        public Future testTask03() {
            log.info("任务3开启");
            try {
                Thread.sleep(1500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("任务3结束");
            return new AsyncResult<>("success");
        }

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

3.实验验证

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

    @Override
    @Async("AsyncTask")
    public void testTask02() {
        log.info("任务2开启");
        try {
            Thread.sleep(1500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("任务2结束");
    }

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

    @GetMapping("/test")
    public void test(){
        long start = System.currentTimeMillis();
        userAddressService.testTask01();
        userAddressService.testTask02();
        long end = System.currentTimeMillis();
        log.info("future(): 耗时" + (end - start) + "ms");
    }

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

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

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

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

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

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

springboot整合异步任务——@Async注解_第1张图片 可以看到10个任务同时异步开始执行。

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

4.注意事项

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

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

你可能感兴趣的:(java,spring,boot,spring)