欢迎关注头条号:老顾聊技术
精品技术文章分享,知识的组装工
目录
- 什么是异步调用?
- 如何实现异步调用?
- 结合Spring boot实现异步
- 如何知道异步调用什么时候完成?
- 异步线程池
- 异步回调
- 异常处理
上一篇文章中,我们介绍了不一样的异步场景,这篇我们介绍一下常用的异步方式。
什么是异步调用?
异步调用是相对于同步调用而言的,同步调用是指程序按预定顺序一步步执行,每一步必须等到上一步执行完后才能执行,异步调用则无需等待上一步程序执行完即可执行。
如何实现异步调用?
多线程,这是很多人第一眼想到的关键词,没错,多线程就是一种实现异步调用的方式。
在非spring目项目中我们要实现异步调用的就是使用多线程方式,可以自己实现Runable接口或者集成Thread类,或者使用jdk1.5以上提供了的Executors线程池。
结合SpringBoot实现异步
SpringBoot中则提供了很方便的方式执行异步调用。
- 第一步:启动类:添加@EnableAsync注解
@SpringBootApplication@EnableAsyncpublic class Demo1Application {public static void main(String[] args) {SpringApplication.run(Demo1Application.class, args);}}
- 第二步:新建AsyncController,在方法上添加@Async注解
@RestController
@RequestMapping("/async")
public class AsyncController {
@RequestMapping("/task")
public String doTask() throws InterruptedException{
long currentTimeMillis = System.currentTimeMillis();
this.task1();
this.task2();
this.task3();
long currentTimeMillis1 = System.currentTimeMillis();
System.out.println("task任务总耗时:"+(currentTimeMillis1-currentTimeMillis)+"ms");
return "task任务总耗时:"+(currentTimeMillis1-currentTimeMillis)+"ms";
}
@Async
public void task1() throws InterruptedException{
long currentTimeMillis = System.currentTimeMillis();
Thread.sleep(1000);
long currentTimeMillis1 = System.currentTimeMillis();
System.out.println("task1任务耗时:"+(currentTimeMillis1-currentTimeMillis)+"ms");
}
@Async
public void task2() throws InterruptedException{
long currentTimeMillis = System.currentTimeMillis();
Thread.sleep(2000);
long currentTimeMillis1 = System.currentTimeMillis();
System.out.println("task2任务耗时:"+(currentTimeMillis1-currentTimeMillis)+"ms");
}
@Async
public void task3() throws InterruptedException{
long currentTimeMillis = System.currentTimeMillis();
Thread.sleep(3000);
long currentTimeMillis1 = System.currentTimeMillis();
System.out.println("task3任务耗时:"+(currentTimeMillis1-currentTimeMillis)+"ms");
}
}
在doTask方法中,调用3个异步方法,观察启动结果
我们发现,并没有按照我们的需求,做异步调用,这是什么原因呢?同一个类中,方法调用是在类体内执行的,spring无法截获这个方法调用。
改造思路:将异步任务单独放到一个类中
新建异步任务类
@Componentpublic class AsyncTask { @Async public void task1() throws InterruptedException{ long currentTimeMillis = System.currentTimeMillis(); Thread.sleep(1000); long currentTimeMillis1 = System.currentTimeMillis(); System.out.println("task1任务耗时:"+(currentTimeMillis1-currentTimeMillis)+"ms"); } @Async public void task2() throws InterruptedException{ long currentTimeMillis = System.currentTimeMillis(); Thread.sleep(2000); long currentTimeMillis1 = System.currentTimeMillis(); System.out.println("task2任务耗时:"+(currentTimeMillis1-currentTimeMillis)+"ms"); } @Async public void task3() throws InterruptedException{ long currentTimeMillis = System.currentTimeMillis(); Thread.sleep(3000); long currentTimeMillis1 = System.currentTimeMillis(); System.out.println("task3任务耗时:"+(currentTimeMillis1-currentTimeMillis)+"ms"); } }
修改AsyncController
@RestController@RequestMapping("/async")public class AsyncController {@Autowired private AsyncTask asyncTask; @RequestMapping("/task") public String doTask() throws InterruptedException{ long currentTimeMillis = System.currentTimeMillis(); asyncTask.task1(); asyncTask.task2(); asyncTask.task3(); long currentTimeMillis1 = System.currentTimeMillis(); System.out.println("task任务总耗时:"+(currentTimeMillis1-currentTimeMillis)+"ms"); return "task任务总耗时:"+(currentTimeMillis1-currentTimeMillis)+"ms"; } }
再启动观察效果,
我们可以看到主任务很快执行完成,只花了35ms,而其他任务在后台执行,而是各自不同的线程里面执行。这个就达到了异步调用,可以提升系统的性能。一般我们同学学到这里就结束了,没有继续钻研其他的业务场景;下面老顾介绍一下其他的场景。
如何知道异步调用什么时候完成?
如果我们要想知道三个异步任务什么时候执行完,执行的结果怎样呢?可以采用添加Fature回调方式判断
修改AsyncTask类,增加Future返回值
@Componentpublic class AsyncTask {@Async public Future task1() throws InterruptedException{ long currentTimeMillis = System.currentTimeMillis(); Thread.sleep(1000); long currentTimeMillis1 = System.currentTimeMillis(); System.out.println("task1任务耗时:"+(currentTimeMillis1-currentTimeMillis)+"ms"); return new AsyncResult("task1返回值");} @Async public Future task2() throws InterruptedException{ long currentTimeMillis = System.currentTimeMillis(); Thread.sleep(2000); long currentTimeMillis1 = System.currentTimeMillis(); System.out.println("task2任务耗时:"+(currentTimeMillis1-currentTimeMillis)+"ms"); return new AsyncResult("task2返回值"); } @Async public Future task3() throws InterruptedException{ long currentTimeMillis = System.currentTimeMillis(); Thread.sleep(3000); long currentTimeMillis1 = System.currentTimeMillis(); System.out.println("task3任务耗时:"+(currentTimeMillis1-currentTimeMillis)+"ms"); return new AsyncResult("task3返回值"); } }
修改AsyncController
@RestController@RequestMapping("/async")public class AsyncController {@Autowired private AsyncTask asyncTask; @RequestMapping("/task") public String doTask() throws InterruptedException{ long currentTimeMillis = System.currentTimeMillis(); Future taskResult1 = asyncTask.task1(); Future taskResult2 = asyncTask.task2(); Future taskResult3 = asyncTask.task3(); while(true) { if(taskResult1.isDone() && taskResult2.isDone() && taskResult3.isDone()) { // 三个任务都调用完成,退出循环等待 break; } Thread.sleep(1000); } long currentTimeMillis1 = System.currentTimeMillis(); System.out.println("task任务总耗时:"+(currentTimeMillis1-currentTimeMillis)+"ms"); return "task任务总耗时:"+(currentTimeMillis1-currentTimeMillis)+"ms"; } }
在启动观察效果
发现总耗时变长了,那是因为主任务一直等待其他线程执行结束。我们再注意观察一下,每执行一次任务异步的线程都会重新创建,而不是从线程池中获取,这样也会导致性能下降。我们再进行改造一下。
异步线程池
定义线程池类SpringAsyncConfig
@Configuration@EnableAsyncpublic class SpringAsyncConfig { //线程池一 @Bean(name="threadPoolTaskExecutor1") public AsyncTaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setMaxPoolSize(10); return executor; } //线程池二 @Bean(name="threadPoolTaskExecutor2") public AsyncTaskExecutor taskExecutor2() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setMaxPoolSize(10); return executor; } }
线程池已经定义好了,怎么使用,就比较简单了。
@Componentpublic class AsyncTask { @Async("threadPoolTaskExecutor1") public Future task1() throws InterruptedException{ long currentTimeMillis = System.currentTimeMillis(); Thread.sleep(1000); long currentTimeMillis1 = System.currentTimeMillis(); System.out.println("task1任务耗时:"+(currentTimeMillis1-currentTimeMillis)+"ms"); return new AsyncResult("task1返回值"); } @Async("threadPoolTaskExecutor1") public Future task2() throws InterruptedException{ long currentTimeMillis = System.currentTimeMillis(); Thread.sleep(2000); long currentTimeMillis1 = System.currentTimeMillis(); System.out.println("task2任务耗时:"+(currentTimeMillis1-currentTimeMillis)+"ms"); return new AsyncResult("task2返回值"); } @Async("threadPoolTaskExecutor2") public Future task3() throws InterruptedException{ long currentTimeMillis = System.currentTimeMillis(); Thread.sleep(3000); long currentTimeMillis1 = System.currentTimeMillis(); System.out.println("task3任务耗时:"+(currentTimeMillis1-currentTimeMillis)+"ms"); return new AsyncResult("task3返回值"); } }
在注解@Async注解里面指定用哪个线程池。再次启动观察,多执行几次可以看到是从线程池中获取的。
异步回调
上面我们解决了线程性能的问题,有时候我们还会遇到需要知道异步调用的返回值,根据不同的值处理不同的业务,那怎么处理呢?
我们发现Future接口中,有个get方法是返回异步调用的返回值,我们想在调用get方法时,判断异步调用是否成功,然后执行不同逻辑的业务;但get方法会把线程阻塞住,又导致了我们的http://localhost:8081/async/task请求变成的同步。我们如何能够实现异步调用成功后,我们执行成功回调方法;调用失败,我们执行失败回调方法。
这个时候我们要用到接口ListenableFuture,此接口是继承Future接口;ListenableFuture接口有addCallback这个方法增加成功回调方法,和失败回调方法。成功回调定义需要实现SuccessCallback这个接口;失败回调定义需要实现FailureCallback这个接口。
异步发送邮件案例,新建Email实体类
package com.rainbow.springboot.demo1.vo;import lombok.Data;@Datapublic class EMail { private String userName; private String emailAddr; private boolean isSuccess = false; //是否发送成功 private String message; public EMail(String username,String emailAddr){ this.userName = username; this.emailAddr = emailAddr; }}
新建发送email任务
@Componentpublic class EMailTask { @Async public ListenableFuture send() throws InterruptedException, AsyncException{ long currentTimeMillis = System.currentTimeMillis(); EMail eMail = new EMail("rainbow