天天写同步,5种SpringMvc异步请求了解下!
引言
说到异步大家肯定首先会先想到同步。我们先来看看什么是同步?
所谓同步,就是发出一个功能调用时,在没有得到结果之前,该调用就不返回或继续执行后续操作。
简单来说,同步就是必须一件一件事做,等前一件做完了才能做下一件事。
异步:异步就相反,调用在发出之后,这个调用就直接返回了,不需要等结果。
浏览器同步
浏览器发起一个request然后会一直待一个响应response,在这期间里面它是阻塞的。比如早期我们在我们在逛电商平台的时候买东西我们打开一个商品的页面,大致流程是不是可能是这样,每次打开一个页面都是由一个线程从头到尾来处理,这个请求需要进行数据库的访问需要把商品价格库存啥的返回页面,还需要去调用第三方接口,比如优惠券接口等我们只有等到这些都处理完成后这个线程才会把结果响应给浏览器,在这等结果期间这个线程只能一直在干等着啥事情也不能干。这样的话是不是会有有一定的性能问题。大致的流程如下:
浏览器异步
为了解决上面同步阻塞的问题,再Servlet3.0发布后,提供了一个新特性:异步处理请求。比如我们还是进入商品详情页面,这时候这个前端发起一个请求,然后会有一个线程来执行这个请求,这个请求需要去数据库查询库存、调用第三方接口查询优惠券等。这时候这个线程就不用干等着呢。它的任务到这就完成了,又可以执行下一个任务了。等查询数据库和第三方接口查询优惠券有结果了,这时候会有一个新的线程来把处理结果返回给前端。这样的话线程的工作量是不超级饱和,需要不停的干活,连休息的机会都不给了。
- 这个异步是纯后端的异步,对前端是无感的,异步也并不会带来响应时间上的优化,原来该执行多久照样还是需要执行多久。但是我们的请求线程(Tomcat 线程)为异步servlet之后,我们可以立即返回,依赖于业务的任务用业务线程来执行,也就是说,Tomcat的线程可以立即回收,默认情况下,Tomcat的核心线程是10,最大线程数是200,我们能及时回收线程,也就意味着我们能处理更多的请求,能够增加我们的吞吐量,这也是异步Servlet的主要作用。
下面我们就来看看Spring mvc 的几种异步方式吧
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-async
在这个之前我们还是先简单的回顾下Servlet 3.1的异步: - 客户端(浏览器、app)发送一个请求
- Servlet容器分配一个线程来处理容器中的一个servlet
- servlet调用request.startAsync()开启异步模式,保存AsyncContext, 然后返回。
- 这个servlet请求线程以及所有的过滤器都可以结束,但其响应(response)会等待异步线程处理结束后再返回。
- 其他线程使用保存的AsyncContext来完成响应
- 客户端收到响应
Callable
/** 公众号:java金融
* 使用Callable
* @return
*/
@GetMapping("callable")
public Callable<String> callable() {
System.out.println(LocalDateTime.now().toString() + "--->主线程开始");
Callable<String> callable = () -> {
String result = "return callable";
// 执行业务耗时 5s
Thread.sleep(5000);
System.out.println(LocalDateTime.now().toString() + "--->子任务线程("+Thread.currentThread().getName()+")");
return result;
};
System.out.println(LocalDateTime.now().toString() + "--->主线程结束");
return callable;
}
public static String doBusiness() {
// 执行业务耗时 10s
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return UUID.randomUUID().toString();
}
- 控制器先返回一个Callable对象
- Spring MVC开始进行异步处理,并把该Callable对象提交给另一个独立线程的执行器TaskExecutor处理
- DispatcherServlet和所有过滤器都退出Servlet容器线程,但此时方法的响应对象仍未返回
- Callable对象最终产生一个返回结果,此时Spring MVC会重新把请求分派回Servlet容器,恢复处理
- DispatcherServlet再次被调用,恢复对Callable异步处理所返回结果的处理
上面就是Callable的一个执行流程,下面我们来简单的分析下源码,看看是怎么实现的:
我们知道SpringMvc是可以返回json格式数据、或者返回视图页面(html、jsp)等,SpringMvc是怎么实现这个的呢?最主要的一个核心类就是org.springframework.web.method.support.HandlerMethodReturnValueHandler 我们来看看这个类,这个类就是一个接口,总共就两个方法;
boolean supportsReturnType(MethodParameter returnType);
void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
上面这个我们的请求是返回Callable 这样一个结果的,我们会根据这个返回的类型去找所有实现了HandlerMethodReturnValueHandler 这个接口的实现类,最终我们会根据返回类型通过supportsReturnType这个实现的方法找到一个对应的HandlerMethodReturnValueHandler 实现类,我们根据返回类型是Callable然后就找到了实现类CallableMethodReturnValueHandler。
开启异步线程的话也就是在handleReturnValue这个方法里面了,感兴趣的大家可以动手去debug下还是比较好调试的。
CompletableFuture 和ListenableFuture
@GetMapping("completableFuture")
public CompletableFuture<String> completableFuture() {
// 线程池一般不会放在这里,会使用static声明,这只是演示
ExecutorService executor = Executors.newCachedThreadPool();
System.out.println(LocalDateTime.now().toString() + "--->主线程开始");
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(IndexController::doBusiness, executor);
System.out.println(LocalDateTime.now().toString() + "--->主线程结束");
return completableFuture;
}
@GetMapping("listenableFuture")
public ListenableFuture<String> listenableFuture() {
// 线程池一般不会放在这里,会使用static声明,这只是演示
ExecutorService executor = Executors.newCachedThreadPool();
System.out.println(LocalDateTime.now().toString() + "--->主线程开始");
ListenableFutureTask<String> listenableFuture = new ListenableFutureTask<>(()-> doBusiness());
executor.execute(listenableFuture);
System.out.println(LocalDateTime.now().toString() + "--->主线程结束");
return listenableFuture;
}
注:这种方式记得不要使用内置的不要使用内置的 ForkJoinPool线程池,需要自己创建线程池否则会有性能问题
WebAsyncTask
@GetMapping("asynctask")
public WebAsyncTask asyncTask() {
SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor();
System.out.println(LocalDateTime.now().toString() + "--->主线程开始");
WebAsyncTask<String> task = new WebAsyncTask(1000L, executor, ()-> doBusiness());
task.onCompletion(()->{
System.out.println(LocalDateTime.now().toString() + "--->调用完成");
});
task.onTimeout(()->{
System.out.println("onTimeout");
return "onTimeout";
});
System.out.println(LocalDateTime.now().toString() + "--->主线程结束");
return task;
}
DeferredResult
@GetMapping("deferredResult")
public DeferredResult<String> deferredResult() {
System.out.println(LocalDateTime.now().toString() + "--->主线程("+Thread.currentThread().getName()+")开始");
DeferredResult<String> deferredResult = new DeferredResult<>();
CompletableFuture.supplyAsync(()-> doBusiness(), Executors.newFixedThreadPool(5)).whenCompleteAsync((result, throwable)->{
if (throwable!=null) {
deferredResult.setErrorResult(throwable.getMessage());
}else {
deferredResult.setResult(result);
}
});
// 异步请求超时时调用
deferredResult.onTimeout(()->{
System.out.println(LocalDateTime.now().toString() + "--->onTimeout");
});
// 异步请求完成后调用
deferredResult.onCompletion(()->{
System.out.println(LocalDateTime.now().toString() + "--->onCompletion");
});
System.out.println(LocalDateTime.now().toString() + "--->主线程("+Thread.currentThread().getName()+")结束");
return deferredResult;
}
- 上面这几种异步方式都是会等到业务doBusiness执行完之后(10s)才会把response给到前端,执行请求的主线程会立即结束,响应结果会交给另外的线程来返回给前端。
- 这种异步跟下面的这个所谓的假异步是不同的,这种情况是由主线程执行完成之后立马返回值(主线程)给前端,不会等个5s在返回给前端。
@GetMapping("call")
public String call() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
return "这是个假异步";
}
这几种异步方式都跟返回Callable 差不多,都有对应的HandlerMethodReturnValueHandler 实现类,无非就是丰富了自己一些特殊的api、比如超时设置啥的,以及线程池的创建是谁来创建,执行流程基本都是一样的。
总结
- 了解spring mvc 的异步编程,对我们后续学习响应式编程、rxjava、webflux等都是有好处的。
- 异步编程可以帮我们高效的利用系统资源。
结束
- 由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。
- 如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。
- 感谢您的阅读,十分欢迎并感谢您的关注。
站在巨人的肩膀上摘苹果:
https://blog.csdn.net/f641385712/article/details/88692534
天天写同步,5种SpringMvc异步请求了解下!的更多相关文章
- 使用Callable或DeferredResult实现springmvc的异步请求
使用Callable实现springmvc的异步请求 如果一个请求中的某些操作耗时很长,会一直占用线程.这样的请求多了,可能造成线程池被占满,新请求无法执行的情况.这时,可以考虑使用异步请求,即主线程 ...
- Springmvc中 同步/异步请求参数的传递以及数据的返回
转载:http://blog.csdn.net/qh_java/article/details/44802287 注意: 这里的返回就是返回到jsp页面 **** controller接收前台数据的方 ...
- springmvc中同步/异步请求参数的传递以及数据的返回
注意: 这里的返回就是返回到jsp页面 **** controller接收前台数据的方式,以及将处理后的model 传向前台***** 1.前台传递数据的接受:传的属性名和javabean的属性相同 ...
- iOS开发-Get请求,Post请求,同步请求和异步请求
标题中的Get和Post是请求的两种方式,同步和异步属于实现的方法,Get方式有同步和异步两种方法,Post同理也有两种.稍微有点Web知识的,对Get和Post应该不会陌生,常说的请求处理响应,基本 ...
- ASIHTTP 框架,同步、 异步请求、 上传 、 下载
ASIHTTPRequest详解 ASIHTTPRequest 是一款极其强劲的 HTTP 访问开源项目.让简单的 API 完成复杂的功能,如:异步请求,队列请求,GZIP 压缩,缓存,断点续传,进度 ...
- Http中的同步请求和异步请求
最近在上springmvc的JSON数据交换的时候,老师下课提了一个课后问题:什么是异步请求?什么是同步请求?我想大部分同学听到这个问题的时候应该和我一样不知所云.现在,给大家分享一篇关于同步请求和异 ...
- 详细解读XMLHttpRequest(一)同步请求和异步请求
本文主要参考:MDN XMLHttpRequest 让发送一个HTTP请求变得非常容易.你只需要简单的创建一个请求对象实例,打开一个URL,然后发送这个请求.当传输完毕后,结果的HTTP状态以及返回的 ...
- IOS之同步请求、异步请求、GET请求、POST请求
.同步请求可以从因特网请求数据,一旦发送同步请求,程序将停止用户交互,直至服务器返回数据完成,才可以进行下一步操作, .异步请求不会阻塞主线程,而会建立一个新的线程来操作,用户发出异步请求后,依然可以 ...
- IOS - IOS之同步请求、异步请求、GET请求、POST请求
1.同步请求可以从因特网请求数据,一旦发送同步请求,程序将停止用户交互,直至服务器返回数据完成,才可以进行下一步操作, 2.异步请求不会阻塞主线程,而会建立一个新的线程来操作,用户发出异步请求后,依然 ...
随机推荐
- [Skill]加速npm与yarn还原
npm源 使用cnpm alias cnpm="npm --registry=https://registry.npm.taobao.org //或者 npm install -g cnpm ...
- linux mysql source 导入大文件报错解决办法
找到mysql的配置文件目录 my.cnf interactive_timeout = 120wait_timeout = 120max_allowed_packet = 500M 在导入过程中可能会 ...
- 【C++】《C++ Primer 》第五章
第五章 语句 一.简单语句 表达式语句:一个表达式末尾加上分号,就变成了表达式语句. 空语句:只有一个单独的分号,记得注释说明提高代码可读性. 复合语句(块):用花括号 {}包裹起来的语句和声明的序列 ...
- LeetCode278 第一个错误的版本
你是产品经理,目前正在带领一个团队开发新的产品.不幸的是,你的产品的最新版本没有通过质量检测.由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的. 假设你有 n 个版本 [1, ...
- 一条查询SQl是怎样执行的
MySQL的逻辑架构图 大体来说,MySQL可以分为Server层和存储引擎层两部分. Server层包括连接器.查询缓存.分析器,优化器等,涵盖MySQL的大多核心服务功能,以及所有的内置函数,存储 ...
- linux源码安装软件的一般方法
rhel系统貌似安装不了xmgrace,配置的时候居然说要那个M*tif库.百度了一下,需要openmotif库,然后用root账户想要用yum安装一下这个库,搞了好久没搞懂.后面搞明白了,原因竟是因 ...
- ABAP关键字和ABAP词汇
下表为ABAP的词汇概览(包括关键字): ABAP-SOURCE ABBREVIATED ABS ABSTRACT ACCEPT ACCEPTING ACCORDING ACOS ACTIVATION ...
- Cookie&Session&Jsp总结
知识点梳理 Cookie&Session&Jsp 1 会话技术 1.1 会话管理概述 1.1.1 会话技术介绍 会话:浏览器和服务器之间的多次请求和响应 (一次对话) 为了实现一些功能 ...
- vue中computed/method/watch的区别
摘要:本文通过官方文档结合源码来分析computed/method/watch的区别. Tips:本文分析的源码版本是v2.6.11,文章中牵涉到vue响应式系统原理部分,如果不是很了解,建议先阅读上 ...
- uni-app 获取地址位置
uni.getLocation 获取当前的地理位置.速度. 在微信小程序中,当用户离开应用后,此接口无法调用:当用户点击"显示在聊天顶部"时,此接口可继续调用 uni.getLoc ...