SpringBoot-Async异步功能

x33g5p2x  于2021-09-24 转载在 Spring  
字(6.6k)|赞(0)|评价(0)|浏览(348)

引言: 在Java应用中,绝大多数情况下都是通过同步的方式来实现交互处理的;但是在处理与第三方系统交互的时候,容易造成响应迟缓的情况,之前大部分都是使用多线程来完成此类任务

1. 何为异步调用?

在解释异步调用之前,我们先来看同步调用的定义;同步就是整个处理过程顺序执行,当各个过程都执行完毕,并返回结果。 异步调用则是只是发送了调用的指令,调用者无需等待被调用的方法完全执行完毕;而是继续执行下面的流程。

例如, 在某个调用中,需要顺序调用 A, B, C三个过程方法;如他们都是同步调用,则需要将他们都顺序执行完毕之后,方算作过程执行完毕; 如B为一个异步的调用方法,则在执行完A之后,调用B,并不等待B完成,而是执行开始调用C,待C执行完毕之后,就意味着这个过程执行完毕了。

2. 常规的异步调用处理方式

在Java中,一般在处理类似的场景之时,都是基于创建独立的线程去完成相应的异步调用逻辑,通过主线程和不同的线程之间的执行流程,从而在启动独立的线程之后,主线程继续执行而不会产生停滞等待的情况。

而spring boot 种提供了更加简单的方式不用我们自己去创建了,只需要很简单的在方法上添加一个@Async注解那么就可以实现异步了,这种方式的原理是基于AOP思想…下面就来学习基于@Async注解的方式

3. @Async介绍

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

基于SpringBoot配置的启用方式:

@Configuration
public class MyAsyncConfigurer implements AsyncConfigurer {
    @Bean("taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor  executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        //最大线程数30
        executor.setMaxPoolSize(30);
        //缓冲队列200:用来缓冲执行任务的队列
        executor.setQueueCapacity(200);
        // 允许线程的空闲时间60秒:超过了核心线程数之外的线程,在空闲时间到达之后会被销毁
        executor.setKeepAliveSeconds(60);
        //线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
        executor.setThreadNamePrefix("taskExecutor-");
        //设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住
        executor.setWaitForTasksToCompleteOnShutdown(true);
        //设置线程池中任务的等待时间
        executor.setAwaitTerminationSeconds(60);
        //当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

        return executor;
    }

然后在启动类上添加

@EnableAsync

4.@Async 无效的情况

没有在启动类上添加@EnableAsync
1.
因为aop代理的缘故,被调用方法和调用处的代码都处在同一个类,所以只是相当于本类调用,并没有使用代理类 从而@Async并没有产生效果。
1.
异步方法使用注解@Async的返回值只能为void或者Future。
1.
@Async注解的方法必须是public方法。

5. 基于@Async无返回值调用

接口

@GetMapping("/testAsync1")
    @Async
    public  ResponseEntity<String>  testAsync1(@PathVariable String string) {

        System.out.println("接口执行开始===========================");
        testAsync.asyncMethodWithVoidReturnType(); //需要异步的方法
        System.out.println("这个接口执行完毕=========================");

        return ResponseEntity.ok("这个接口执行完毕=========================");
    }

异步方法

@Component
public class TestAsync {

    @Async("taskExecutor") //标注使用异步
    public void asyncMethodWithVoidReturnType() {
        System.out.println("异步方法开始");
        try {
            Thread.sleep(5000);//5秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("异步方法结束");
    }
}

控制台执行效果:

接口执行开始===========================
这个接口执行完毕=========================
异步方法开始
异步方法结束

使用的方式非常简单,一个标注即可解决所有的问题。

6. 基于@Async返回值的调用

这种方式一般用的比较少,用的最多的就是提升同步接口下响应的速度,

比如: 有些添加数据时候前端返回一个大的对象JSON, 里边包含很多小的对象,而添加的时候都是分开添加的互不影响,这样就可以使用异步操作,然后在最后进行监控如果都成功了那么,在响应给前端,随着业务的复杂度提升-速度可以提升n倍

接口

@Autowired
    private TestAsync testAsync;
    //http://localhost:10101/user/testAsync
    @GetMapping("/testAsync2")
    public ResponseEntity<String> testAsync2(){
        long startTime = System.currentTimeMillis();    //获取开始时间
        System.out.println("接口方法执行开始");
        Future<String> future = testAsync.asyncMethodWithReturnType(); //异步执行

        try {
            System.out.println("其他代码执行过程时间");
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Future<String> future1 = testAsync.asyncMethodWithReturnType1();//异步执行

        try {
            System.out.println("其他代码执行过程时间1");
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        boolean a=false,b=false; //创建2个异步方法的控制变量

        while (true) { // 判断异步方法执行 时候成功
            System.out.println("监测是否都执行完毕-------------------------");
            if (future.isDone()&&!a) { //判断异步方法是否执行完毕
                System.out.println("异步方法1执行完毕-开始后期处理");
                a=true;
            }

            if (future1.isDone()&&!b) { //判断异步方法是否执行完毕

                System.out.println("异步方法2执行完毕-开始后期处理");
                b=true;
            }

            if (a&&b) { //如果都执行成功的话那么 跳出循环方法直接结束 ,用户的响应结束
                System.out.println("异步方法1和2都执行完毕-开始后期处理");
                break;
            }

            try {
                Thread.sleep(500); //500毫秒监测一次,不然消耗CPU资源太多了
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }

        System.out.println("接口方法执行结束");
        long endTime = System.currentTimeMillis();    //获取结束时间
        System.out.println("程序运行时间:" + (endTime - startTime) + "ms");    //输出程序运行时间
        return ResponseEntity.ok("方法执行结束");
    }

异步方法

@Component
public class TestAsync {

    @Async("taskExecutor")
    public Future<String> asyncMethodWithReturnType() {
        System.out.println("异步方法开始");

        try {
            Thread.sleep(5000); //执行时间5秒
            //返回执行完成后的内容
            return new AsyncResult<String>("hello world !!!!");
        } catch (InterruptedException e) {
            e.printStackTrace();
            return  new AsyncResult<String>("异步方法-出现问题-结束");
        }
    }

    @Async("taskExecutor")
    public Future<String> asyncMethodWithReturnType1() {
        System.out.println("异步方法开始");

        try {
            Thread.sleep(5000); //执行时间5秒
            //返回执行完成后的内容
            return new AsyncResult<String>("hello world !!!!");
        } catch (InterruptedException e) {
            e.printStackTrace();
            return  new AsyncResult<String>("异步方法-出现问题-结束");
        }
    }

执行效果:

接口方法执行开始
其他代码执行过程时间
异步方法开始
其他代码执行过程时间1
异步方法开始
监测是否都执行完毕-------------------------
监测是否都执行完毕-------------------------
监测是否都执行完毕-------------------------
监测是否都执行完毕-------------------------
异步方法1执行完毕-开始后期处理
监测是否都执行完毕-------------------------
监测是否都执行完毕-------------------------
监测是否都执行完毕-------------------------
异步方法2执行完毕-开始后期处理
异步方法1和2都执行完毕-开始后期处理
接口方法执行结束
程序运行时间:7026ms

然后我们在试试不使用@Async的方法看看执行时间是多少

测试代码如下:

@GetMapping("/testAsync3")  // 12036ms
    public ResponseEntity<String> testAsync3(){
        long startTime = System.currentTimeMillis();    //获取开始时间
        System.out.println("接口方法开始");

        try {
            System.out.println("方法1开始");
            Thread.sleep(5000); //执行时间
            System.out.println("方法1结束");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        try {
            System.out.println("其他代码执行过程时间");
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        try {
            System.out.println("方法2开始");
            Thread.sleep(5000); //执行时间
            System.out.println("方法2结束");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            System.out.println("其他代码执行过程时间1");
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("接口方法执行结束");
        long endTime = System.currentTimeMillis();    //获取结束时间
        System.out.println("程序运行时间:" + (endTime - startTime) + "ms");    //输出程序运行时间
        return ResponseEntity.ok("方法执行结束");
    }

接口方法开始
异步方法开始
异步方法结束
其他代码执行过程时间
异步1方法开始
异步1方法结束
其他代码执行过程时间1
接口方法执行结束
程序运行时间:14024ms

可以看到这个执行时间将近提上了一倍以上,这还是把执行时间写死了,如果不写死差距会更大

7.基于接口直接使用@Async的方式

这种方式是直接让用户请求接口时候直接就能响应,成功,几乎0延迟的方式,

这种方式一般用于 不对数据进行增删改的操作时候使用,

一般都是,用于用户并不需要知道是否执行成功, 前端调用接口后直接默认返回操作成功就行,

急需要用户体验的场景时候,可以使用这种方式.

可能会问了,这种方式和基于@Async无返回值调用有啥区别啊,

@Async无返回值调用

  1. 无返回值是作用在service的返回
  2. 调用接口还会处理其他业务逻辑

这样就会导致接口可能还是无法做到秒响应的效果

一般需要秒响应的场景,不是很多,如果甲方很变态的话,那么也没办法

接口

@GetMapping("/testAsync4")  
    @Async("taskExecutor")
    public void testAsync4(){

        try {
            System.out.println("其他代码执行过程时间1");
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("执行完毕");
        
    }

相关文章