1. SpringMvc使用@Async注解实现有返回值和无返回值的异步处理 异步调用对应的是同步调用,同步调用指程序按照定义顺序依次执行,每一行程序都必须等待上一行程序执行完成之后才能执行;异步调用指程序在顺序执行时,不等待异步调用的语句返回结果就执行后面的程序。
1.1 同步方式调用代码
相关代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Service public class TaskService { public void doTaskOne () throws Exception { System.out.println("开始做任务一" ); long start = System.currentTimeMillis(); Thread.sleep(2000 ); long end = System.currentTimeMillis(); System.out.println("完成任务二,耗时:%s" , (end - start) + "毫秒" ); } public void doTaskTwo () throws Exception { System.out.println("开始做任务二" ); long start = System.currentTimeMillis(); Thread.sleep(3000 ); long end = System.currentTimeMillis(); System.out.println("完成任务二,耗时:%s" , (end - start) + "毫秒" ); } public void doTaskThree () throws Exception { System.out.println("开始做任务三" ); long start = System.currentTimeMillis(); Thread.sleep(4000 ); long end = System.currentTimeMillis(); System.out.println("完成任务二,耗时:%s" , (end - start) + "毫秒" ); } }
同步调用
1 2 3 4 5 6 7 8 9 10 11 @Autowired private TaskService task;public String test () { try { task.doTaskOne(); task.doTaskTwo(); task.doTaskThree(); }catch (Exception e){ } }
下面是运行结果,可以看到三个方法是依次执行的,分别耗时2秒、3秒、4秒、总耗时9秒
1 2 3 4 5 6 开始做任务一 完成任务一,耗时:2001毫秒 开始做任务二 完成任务二,耗时:3000毫秒 开始做任务三 完成任务三,耗时:4001毫秒
上面的同步调用,虽然顺利地完成了三个任务,但是执行时间比较长,如果这三个任务没有依赖关系,可以并发执行的话,可以考虑使用异步调用的方法。
2. 异步方式调用代码无返回值
1 2 3 4 5 6 7 8 9 10 11 12 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:context ="http://www.springframework.org/schema/context" xmlns:task ="http://www.springframework.org/schema/task" xsi:schemaLocation =" http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd" > <task:annotation-driven executor ="taskExecutor" /> <task:executor id ="taskExecutor" pool-size ="20" queue-capacity ="1000" /> </beans >
如果直接按照下面的方式配置,则 Spring 会使用默认的线程池 org.springframework.core.task.SimpleAsyncTaskExecutor
但这个 SimpleAsyncTaskExecutor 不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <task:annotation-driven /> # 详见spring-context-4.1.7.RELEASE.jar/org/springframework/scheduling/config/spring-task-4.0.xsd 的描述 <xsd:attribute name ="executor" type ="xsd:string" use ="optional" > <xsd:annotation > <xsd:documentation > <![CDATA[ Specifies the java.util.Executor instance to use when invoking asynchronous methods. If not provided, an instance of org.springframework.core.task.SimpleAsyncTaskExecutor will be used by default. Note that as of Spring 3.1.2, individual @Async methods may qualify which executor to use, meaning that the executor specified here acts as a default for all non-qualified @Async methods. ]]></xsd:documentation > </xsd:annotation >
在方法上加上 @Async 注解就能将同步函数变成异步函数,改造后的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import java.util.concurrent.Future;@Service public class TaskService { @Async public void doTaskOne () throws Exception { System.out.println("开始做任务一" ); long start = System.currentTimeMillis(); Thread.sleep(2000 ); long end = System.currentTimeMillis(); System.out.println("完成任务二,耗时:%s" , (end - start) + "毫秒" ); } @Async public void doTaskTwo () throws Exception { System.out.println("开始做任务二" ); long start = System.currentTimeMillis(); Thread.sleep(3000 ); long end = System.currentTimeMillis(); System.out.println("完成任务二,耗时:%s" , (end - start) + "毫秒" ); } @Async public void doTaskThree () throws Exception { System.out.println("开始做任务三" ); long start = System.currentTimeMillis(); Thread.sleep(4000 ); long end = System.currentTimeMillis(); System.out.println("完成任务二,耗时:%s" , (end - start) + "毫秒" ); } }
1 2 3 4 5 6 开始做任务三 开始做任务二 开始做任务一 完成任务一,耗时:2000毫秒 完成任务二,耗时:3001毫秒 完成任务三,耗时:4000毫秒
注意事项
3. 异步方式调用代码有返回值 如果想知道异步函数什么时候执行完,那就需要使用 Future (AsyncResult是Future的子类)来返回异步调用的结果。 改造后的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @Service public class TaskService { @Async public Future<String> doTaskOne () { System.out.println("开始做任务一" ); long start = System.currentTimeMillis(); Thread.sleep(2000 ); long end = System.currentTimeMillis(); System.out.println("完成任务二,耗时:%s" , (end - start) + "毫秒" ); return new AsyncResult<>("任务一完成" ); } @Async public Future<String> doTaskTwo () { System.out.println("开始做任务二" ); long start = System.currentTimeMillis(); Thread.sleep(3000 ); long end = System.currentTimeMillis(); System.out.println("完成任务二,耗时:%s" , (end - start) + "毫秒" ); return new AsyncResult<>("任务二完成" ); } @Async public Future<String> doTaskThree () { System.out.println("开始做任务三" ); long start = System.currentTimeMillis(); Thread.sleep(4000 ); long end = System.currentTimeMillis(); System.out.println("完成任务二,耗时:%s" , (end - start) + "毫秒" ); return new AsyncResult<>("任务三完成" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Autowired private TaskService task;public List<String> test () { List<String> result = new ArrayList(16 ); StopWatch stopWatch = new StopWatch(); stopWatch.start("接口速度统计" ); int timeout = 3 ; try { Future<String> task1 = task.doTaskOne(); Future<String> task2 = task.doTaskTwo(); Future<String> task3 = task.doTaskThree(); String taskString1= task1.get(timeout, TimeUnit.SECONDS); String taskString2= task2.get(timeout, TimeUnit.SECONDS); String taskString3= task3.get(timeout, TimeUnit.SECONDS); result.add(taskString1); result.add(taskString2); result.add(taskString3); }catch (TimeoutException | InterruptedException | ExecutionException e){ return result; } finally { stopWatch.stop(); log.info(stopWatch.prettyPrint()); } return result; }
1 2 3 4 5 6 7 8 9 10 11 12 13 开始做任务三 开始做任务二 开始做任务一 完成任务一,耗时:2001毫秒 完成任务二,耗时:3000毫秒 完成任务三,耗时:4001毫秒 StopWatch '' : running time (millis) = 248 ----------------------------------------- ms % Task name ----------------------------------------- 04036 100% 接口速度统计
刚开始想利用CountDownLatch来实现等待所有线程结束整合结果,后来调整为 Future 的 get(long timeout, TimeUnit unit) 来实现线程的超时控制,我看有些的例子使用死循环来阻塞整合线程的执行结果,这样做是有些问题的,如果有个线程一直没有结束运行,那就完犊子了!
这是一种常见的场景将一个大的任务切分为数个子任务,并行处理所有子任务,当所有子任务都成功结束时再继续处理后面的逻辑。还有一种做法是利用CountDownLatch, 主线程构造countDownLatch对象,latch的大小为子任务的总数,每一个任务持有countDownLatch的引用,任务完成时对latch减1,主线程阻塞在countDownLatch.await方法上,当所有子任务都成功执行完后,latch=0, 主线程继续执行。
总结 异步调用可以提升接口性能。比如导出下载、发送邮件短信等代码,可以使用异步执行。
参考 https://blog.csdn.net/qqfo24/article/details/81383022