今天一起学习下如何在Spring中进行异步编程。我们都知道,web服务器处理请求 request 的线程是从线程池中获取的,这也不难解释,因为当web请求并发数非常大时,如何一个请求进来就创建一条处理线程,由于创建线程和线程上下文切换的开销是比较大的,web服务器最终将面临崩溃。另外,web服务器创建的处理线程从头到尾默认是同步执行的,也就是说,假如处理线程A负责处理请求B,那么当B没有 return 之前,处理线程A是不可以脱身去处理别的请求的,这将极大限制了web服务器的并发处理能力。
因此线程池解决了线程可循环利用的问题,那同步处理请求怎么去解决呢?答案是异步处理。什么是异步处理呢?异步处理主要是让上面的B请求处理完成之前,能够将A线程空闲出来继续去处理别的请求。那么我们可以这样做,在A线程内部重新开启一个线程C去执行任务,让A直接返回给web服务器,继续接受新进来的请求。
在开始下面的讲解之前,我在这里先区别下两个概念:
1、处理线程
处理线程属于web服务器,负责处理用户请求,采用线程池管理
2、异步线程
异步线程属于用户自定义的线程,可采用线程池管理
spring中提供了对异步任务的支持,采用 WebAsyncTask 类即可实现异步任务,同时我们也可以对异步任务设置相应的回调处理,如当任务超时、抛出异常怎么处理等。异步任务通常非常实用,比如我们想让一个可能会处理很长时间的操作交给异步线程去处理,又或者当一笔订单支付完成之后,开启异步任务查询订单的支付结果。
一、正常异步任务
为了演示方便,异步任务的执行采用 Thread.sleep(long) 模拟,现在假设用户请求以下接口 :
http://localhost:7000/demo/getUserWithNoThing.json
异步任务接口定义如下:
/**
* 测试没有发生任何异常的异步任务
*/
@RequestMapping(value = "getUserWithNoThing.json", method = RequestMethod.GET)
public WebAsyncTask getUserWithNoThing() {
// 打印处理线程名
System.err.println("The main Thread name is " + Thread.currentThread().getName());
// 此处模拟开启一个异步任务,超时时间为10s
WebAsyncTask task1 = new WebAsyncTask(10 * 1000L, () -> {
System.err.println("The first Thread name is " + Thread.currentThread().getName());
// 任务处理时间5s,不超时
Thread.sleep(5 * 1000L);
return "任务1顺利执行成功!任何异常都没有抛出!";
});
// 任务执行完成时调用该方法
task1.onCompletion(() -> {
System.err.println("任务1执行完成啦!");
});
System.err.println("task1继续处理其他事情!");
return task1;
}
控制台打印如下:
The main Thread name is http-nio-7000-exec-1
task1继续处理其他事情!
The first Thread name is MvcAsync1
任务1执行完成啦!
浏览器结果如下:
二、抛异常异步任务
接口调用