SpringBoot 注解 @Async 用法以及注意事项

一 、@Async 的使用方式介绍

1、 了解 @Async

    在 java 应用中,绝大多数情况下都是通过同步的方式来实现交互处理的;但是在处理与第三方系统交互的时候,容易造成响应迟缓的情况,之前大部分都是使用多线程来完成此类任务,其实,在spring 3.x之后,就已经内置了@Async来完美解决这个问题,本文将完成介绍@Async的用法。

    

2、 何为异步调用?

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

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

 

3、常规的异步调用处理方式

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

 

4、@Async介绍

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

 

5、如何在Spring中启用@Async

      基于SpringBoot配置的启用方式:在 SpringBoot 的启动类上开启异步注解支持  @EnableAsync

@SpringBootApplication
@EnableAsync
public class DemoApplication {
  
  public static void main(String[] args){
    
SpringApplication.run(DemoApplication.class, args);
  }
}

 

  基于XML配置文件的启用方式,配置如下:

<task:executor id="myexecutor" pool-size="5"  />
<task:annotation-driven executor="myexecutor"/>

 

6、基于@Async无返回值调用 (多用此方法)

 

@Async  //标注使用
public void asyncMethodWithVoidReturnType() {
    System.out.println("开启一个新线程,线程名字叫: " + Thread.currentThread().getName());
}

 

 7、基于@Async 有返回值的调用

 

@Async
public String asyncMethodWithReturnType() {
    System.out.println("开启一个新线程,线程名字叫: " + Thread.currentThread().getName());
    try {
        Thread.sleep(5000);
        return new String("hello world !!!!");
    } catch (InterruptedException e) {
    }
    return null;
}

 

 

二、@Async 的使用注意事项

  在实际的项目中,对于一些用时比较长的代码片段或者函数,我们可以采用异步的方式来执行,这样就不会影响整体的流程了。比如我在一个用户请求中需要上传一些文件,但是上传文件的耗时会相对来说比较长,这个时候如果上传文件的成功与否不影响主流程的话,就可以把上传文件的操作异步化,在spring boot中比较常见的方式就是把要异步执行的代码片段封装成一个函数,然后在函数头使用@Async注解,就可以实现代码的异步执行。 让我来复述一下吧!

  首先新建一个spring boot项目

package com.example.demo;

@SpringBootApplication
@EnableAsync
public class DemoApplication {

    public static void main(String[] args){
        SpringApplication.run(DemoApplication.class, args);  
    }
}

 

 

  新建一个类Task,用来放三个异步任务doTaskOne、doTaskTwo、doTaskThree:

package com.example.demo.service;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.util.Random;

@Component
public class Task {
    public static Random random =  new Random();

    @Async
    public void doTaskOne() throws InterruptedException {
        System.out.println("开始做任务一");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务一,耗时:"+ (end - start));
    }

    @Async
    public void doTaskTwo() throws Exception {
        System.out.println("开始做任务二");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
    }

    @Async
    public void doTaskThree() throws InterruptedException {
        System.out.println("开始做任务三");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
    }
}

 

 为了让这三个方法执行完,我们需要再单元测试用例上的最后一行加上一个延时,不然等函数退出了,异步任务还没执行完。

在主启动类上测试,测试这三个方法的执行过程:

package com.example.demo;


import com.example.demo.service.UseAsync;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class DemoApplication {

    public static void main(String[] args) throws Exception {
        ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
        Task task = context.getBean("task", Task.class);
        task.doTaskOne();
        task.doTaskTwo();
        task.doTaskThree();
        Thread.sleep(10000);
        System.out.println("血魔");
    }

}

 

 看看我随意的几次测试效果:

SpringBoot 注解 @Async 用法以及注意事项   SpringBoot 注解 @Async 用法以及注意事项

 

 

 我们看到三个任务确实是异步执行的,那我们再看看错误的使用方法。

 

在调用这些异步方法的类中写一个调用这些被@Async注释方法的方法。

package com.example.demo.service;


import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.util.Random;

@Component
public class Task {

    public static Random random =  new Random();

    public void useAsync() throws Exception {
        doTaskOne();
        doTaskTwo();
        doTaskThree();
    }

    @Async
    public void doTaskOne() throws InterruptedException {
        System.out.println("开始做任务一");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务一,耗时:"+ (end - start));
    }

    @Async
    public void doTaskTwo() throws Exception {
        System.out.println("开始做任务二");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
    }

    @Async
    public void doTaskThree() throws InterruptedException {
        System.out.println("开始做任务三");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
    }
}

 

然后在启动类调用 这个方法

package com.example.demo;


import com.example.demo.service.Task;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class DemoApplication {

    public static void main(String[] args) throws Exception {
        ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
        Task task = context.getBean("task", Task.class);
        task.useAsync();
        Thread.sleep(5000);
        System.out.println("血魔");
    }

}

 

然后我们来看看这个方法的执行结果:(多实验几次)

SpringBoot 注解 @Async 用法以及注意事项  SpringBoot 注解 @Async 用法以及注意事项  SpringBoot 注解 @Async 用法以及注意事项

 

  

 可以看出这些方法并没用异步执行,所以 这种在类中调用 本类中被@Async 修饰的方法就不会被异步执行, 所以 @Async 方法尽量放在单独的类中,而不要挤在 冗余的代码中。

 

   

 

上一篇:大家都说不建议直接使用 @Async 注解?为什么??


下一篇:数据结构——顺序栈