JAVA和Nginx 教程大全

网站首页 > 精选教程 正文

Spring Boot中使用@Async实现异步调用

wys521 2024-11-19 12:52:10 精选教程 21 ℃ 0 评论

一、异步调用?

1.1、同步调用

程序按定义的顺序依次执行的过程,每一行代码执行过程必须等待上一行代码执行完毕后才执行。

1.2异步调用指

程序在执行时,无需等待执行的返回值可继续执行后面的代码。显而易见,同步有依赖相关性,而异步没有,所以异步可并发执行,可提高执行效率,在相同的时间做更多的事情。

同步调用示例

Bash
public class Test {

    public static Random random = new Random();

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

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

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

    public static void main(String[] args) throws Exception {
        doTaskFirst();
        doTaskSecond();
        doTaskThird();
    }

}
Bash
开始做任务一
完成任务一,耗时:0毫秒
开始做任务二
完成任务二,耗时:23毫秒
开始做任务三
完成任务三,耗时:5毫秒

Process finished with exit code 0

Async异步调用

在SpringBoot中使用异步调用是很简单的,只需要使用@Async注解即可实现方法的异步调用。

注意:需要在启动类或配置类加入@EnableAsync使异步调用@Async注解生效。

@EnableAsync
@SpringBootApplication
public class LzthApplication extends SpringBootServletInitializer{

使用@Async很简单,只需要在需要异步执行的方法上加入此注解即可。简单示例下

@Component
public class FileDownloadListener {

    private static Logger log = LoggerFactory.getLogger(FileDownloadListener.class);
    @Resource(name = "fileService")
    public FileService fileService;
		
		/**
    * taskExecutor为线程池可无需配置
    */
    @Async(value = "taskExecutor")
    @EventListener(FileEvent.class)
    public void saveDownloadRecord(FileEvent fileEvent){
        log.info("文件下载记录");
        Map<String, Object> source = (Map) fileEvent.getSource();
        fileService.saveDownloadRecord(source);
    }

}

注意事项:

  1. @Async既可以注解在类上也可以注解在方法上,当声明在类级别上时,调用类中的所有方法都将异步执行。就目标方法签名而言,支持任何参数类型。但是,返回类型被约束为void或java.util.concurrent.Future。
  2. 在默认情况下,未设置TaskExecutor时,默认是使用SimpleAsyncTaskExecutor这个线程池,但此线程不是真正意义上的线程池,因为线程不重用,每次调用都会创建一个新的线程。
  3. 调用的异步方法,不能为同一个类的方法,简单来说,因为Spring在启动扫描时会为其创建一个代理类,而同类调用时,还是调用本身的代理类的,所以和平常调用是一样的。其他的注解如@Cache等也是一样的道理,说白了,就是Spring的代理机制造成的。

异步回调及超时处理

对于一些业务场景下,需要异步回调的返回值时,就需要使用异步回调来完成了。主要就是通过Future进行异步回调。

Future类型?

Future是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果的接口。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。

它的接口定义如下:

public interface Future<V> {
    /**
    * 用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。
    * 参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,
    * 如果设置true,则表示可以取消正在执行过程中的任务。
    * 如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,
    * 即如果取消已经完成的任务会返回false;如果任务正在执行,
    * 若mayInterruptIfRunning设置为true,则返回true,
    * 若mayInterruptIfRunning设置为false,则返回false;
    * 如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
    */
    boolean cancel(boolean mayInterruptIfRunning);
		
		/**
    * 任务是否被取消成功,如果在任务正常完成前被取消成功,则返回true 
    */
    boolean isCancelled();
	
		/**
    * 任务是否已经完成,若任务完成,则返回true
    */
    boolean isDone();
		
		/**
    * 获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回
    */
    V get() throws InterruptedException, ExecutionException;
		
		/**
    * 获取执行结果,如果在指定时间内,还没获取到结果,则抛出TimeoutException,结果返回null
    */
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

Future提供了三种功能:
1、判断任务是否完成;
2、能够中断任务;
3、能够获取任务执行结果。

异步回调

新增具有Future类型返回值的异步方法和接口:

@Async
	public Future<String> asyncRet(){
		log.info("async task invoked -> thread: {}", Thread.currentThread().getName());
		try {
			Thread.sleep(3000);
		}catch (InterruptedException e){
			e.printStackTrace();
		}
		return new AsyncResult<>("Async Result");
	}
@GetMapping("/getAsyncRet")
	public String doAsync() throws ExecutionException, InterruptedException {
		long begin = Clock.systemUTC().millis();
		log.info("start the async task: {}", begin);
		Future<String> future = asyncService.asyncRet();
		while (true){
			if(future.isDone()){
				break;
			}
		}
		long end = Clock.systemUTC().millis();
		log.info("async task time cost: {}", end - begin);
		log.info("finish the async task: {}", end);
		return future.get();
	}

其中AsyncResult是Spring提供的一个Future接口的子类,然后通过isDone方法,判断是否已经执行完毕。此时,控制台输出:

2021-09-16 22:20:59.468  INFO 6672 --- [nio-8080-exec-1] c.b.s.controller.AsyncController         : start the async task: 1547091479468
2021-09-16 22:20:59.473  INFO 6672 --- [ async-task-1-1] com.bo.springboot.service.AsyncService   : async task invoked -> thread: async-task-1-1
2021-09-16 22:20:02.474  INFO 6672 --- [nio-8080-exec-1] c.b.s.controller.AsyncController         : async task time cost: 3006
2021-09-16 22:20:02.474  INFO 6672 --- [nio-8080-exec-1] c.b.s.controller.AsyncController         : finish the async task: 1547091482474

等待3秒后页面显示:

所以,当某个业务功能可以同时拆开一起执行时,可利用异步回调机制有效的减少程序执行时间,提高效率。

超时处理

对于一些需要异步回调的函数,不能无期限地等待下去,所以一般上需要设置超时时间,超时后可将线程释放,而不至于一直堵塞而占用资源。对于Future配置超时,很简单,通过get方法指定超时即可。

@GetMapping("/getAsyncWithTimeout")
public String getAsyncWithTimeout() throws InterruptedException, ExecutionException, TimeoutException {
		Future<String> future = asyncService.asyncRet();
		// 2秒后超时
		return future.get(2000, TimeUnit.MILLISECONDS);
}

超时后,会抛出异常TimeoutException类,此时可进行统一异常捕获即可。

小结

该内容主要是讲解了异步调用的使用及相关配置,如超时,异常等处理。在剥离一些和业务无关的操作时,就可以考虑使用异步调用进行其他无关业务操作,以此提供业务的处理效率。或者一些业务场景下可拆分出多个方法进行同步执行又互不影响时,也可以考虑使用异步调用方式提高执行效率。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表