天天写同步,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.异步请求不会阻塞主线程,而会建立一个新的线程来操作,用户发出异步请求后,依然 ...
随机推荐
- Vue3.0聊天室|vue3+vant3仿微信聊天实例|vue3.x仿微信app界面
一.项目简介 基于Vue3.0+Vant3.x+Vuex4.x+Vue-router4+V3Popup等技术开发实现的仿微信手机App聊天实例项目Vue3-Chatroom.实现了发送图文表情消息/g ...
- 风炫安全WEB安全学习第二十六节课 XSS常见绕过防御技巧
风炫安全WEB安全学习第二十六节课 XSS常见绕过防御技巧 XSS绕过-过滤-编码 核心思想 后台过滤了特殊字符,比如说
- 风炫安全WEB安全学习第二十三节课 利用XSS获取COOKIE
风炫安全WEB安全学习第二十三节课 利用XSS获取COOKIE XSS如何利用 获取COOKIE 我们使用pikachu写的pkxss后台 使用方法: <img src="http:/ ...
- MFC(c++大作业)基本对话框的使用(求平均成绩)
OOPEx2Dlg.cpp // OOPEx2Dlg.cpp : 实现文件 // #include "stdafx.h" #include "OOPEx2.h" ...
- 深入理解nodejs中的异步编程
目录 简介 同步异步和阻塞非阻塞 javascript中的回调 回调函数的错误处理 回调地狱 ES6中的Promise 什么是Promise Promise的特点 Promise的优点 Promise ...
- Xshell与Xftp免费下载安装及步骤
Xshell与Xftp免费下载安装及步骤 1.进入Xshell的官网:https://www.netsarang.com/zh/ 加粗样式 2.选择你需要的软件进行下载如:Xshell 3.选择家庭和 ...
- 【Linux】ntp的一些坑。你肯定遇到过
ntpdate提示 19 Jan 10:33:11 ntpdate[29616]: no server suitable for synchronization found 这种问题从下面几个点开始验 ...
- 【Oracle】查看某个角色中有什么权限
select * from role_sys_privs where role='DBA'; 查看dba都有什么系统权限 select * from role_sys_privs where rol ...
- SSRF - Pikachu
概述: SSRF(Server-Side Request Forgery:服务器端请求伪造) 其形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能,但又没有对目标地址做严格过滤与限制 导致 ...
- Hadoop 专栏 - MapReduce 入门
MapReduce的基本思想 先举一个简单的例子: 打个比方我们有三个人斗地主, 要数数牌够不够, 一种最简单的方法可以找一个人数数是不是有54张(传统单机计算); 还可以三个人各分一摞牌数各自的(M ...