小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
写在前面
今天我们来聊聊老生常谈的异步、并发问题,异步是解决并发的最有效的方法。解决耗时让问题,让查询数据库、rpc远程调用等各种耗时操作能够同时进行,达到让接口快速响应的目的,进而有效解决并发问题。
好,话不多说,我们进入今天的正题,不知道有同学用过没 DeferredResult
今天我就跟大家来聊聊如果利用 DeferredResult 实现异步编程,欢迎大家看下我以前写过的一篇
Servlet
讲DeferredResult之前。我们先聊聊 Servlet,这个大家都不陌生,项目中不论你使用struts2,还是使用的springmvc,本质上都是封装的servlet。
在servlet3.0之后支持了异步化,我们采用异步化代码如下:
//测试servlet 异步化
//asyncSupported =true 开启异步
@WebServlet(urlPatterns = "/testSyncServlet",asyncSupported =true)
public class WorkServlet extends HttpServlet{
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置ContentType,关闭缓存
resp.setContentType("text/plain;charset=UTF-8");
resp.setHeader("Cache-Control","private");
resp.setHeader("Pragma","no-cache");
final PrintWriter writer= resp.getWriter();
writer.println("servlet 异步化");
writer.flush();
List<String> textArr=new ArrayList<String>();
for (int i = 0; i < 10; i++) {
textArr.add("zuoye"+i);;
}
//开启异步请求
final AsyncContext ac=req.startAsync();
doProcess(ac, textArr);
writer.println("servlet 异步化完成");
writer.flush();
}
private void doProcess(final AsyncContext ac, final List<String> textArr) {
ac.setTimeout(1*60*60*1000L);
ac.start(new Runnable() {
@Override
public void run() {
//通过response获得字符输出流
try {
PrintWriter writer=ac.getResponse().getWriter();
for (String text:textArr) {
writer.println("\""+text+"\"请求处理中");
Thread.sleep(1*1000L);
writer.flush();
}
ac.complete();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
上面最重要的就是 AsyncContext ac=req.startAsync(); 开启异步请求。setTimeout设置超时时间,ac.start 开启线程执行业务请求。
Spring MVC 的异步处理能力
我们都知道,Spring MVC围绕前端控制器模式(Front ControllerPattern)设计,其中中央Servlet DispatcherServlet为请求处理提供共享的路由算法,负责对请求进行路由分派。
DispatcherServlet与任何Servlet一样,需要使用Java配置或webxml根据Servlet规范进行声明和映射。反过来,DispatcherServlet使用Spring配置来发现请求映射、视图解析、异常处理等所需的委托组件。
那么肯定的是 Spring MVC与前面讲解的Servlet 3.0异步请求处理有很深的集成:
-
DeferredResult和Callable作为controller方法中的返回值,并为单个异步返回值提供基本支持。
-
controller可以使用反应式客户端并返回反应式类型,以进行反应式处理。
Spring MVC内部通过调用request.startAsync()将ServletRequest置于异步模式
好,之所以讲这些是为了让大家从头部明白 DeferredResult 是如何与springMvc结合 大家用的都是springboot来跑项目的,所有肯定不会让你只看原生Servlet的。下面介绍我们今天的主角
基于DeferredResult的异步处理
上面提到,一旦在Servlet容器中启用了异步请求处理功能,controller方法就可以使用DeferredResult包装任何支持的方法返回值。
1、创建 DeferredResult 实例
DeferredResult<String> deferredResult = new DeferredResult<String>();
2、设置超时、完成后操作
deferredResult.onTimeout(() -> {
log.info("调用超时");
});
deferredResult.onCompletion(() -> {
log.info("调用完成");
});
3、 开启线程设置结果
new Thread(() > {
try {
//模拟耗时操作
TimeUnit.SECONDS.sleep(10);
//返回OK结果
deferredResult.setResult("ok"}
catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
完整代码
//先定义线程池
private static ThreadPoolExecutor BIZ_POOL = new ThreadPoolExecutor(8,8,1, TimeUnit.SECONDS,new LinkedBlockingQueue<>(1),
new ThreadPoolExecutor.CallerRunsPolicy());
//调用DeferredResult异步机制
@PostMapping("/testSync")
DeferredResult<String> listPostDeferredResult(){
DeferredResult<String> deferredResult = new DeferredResult<String>();
BIZ_POOL.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
deferredResult.setResult("ok");
}catch (Exception e){
e.printStackTrace();
deferredResult.setErrorResult("error");
}
}
});
return deferredResult;
}
总结
上述代码我们创建了一个业务线程池BIZ_POOL,然后controller方法在listPostDeferredResult内创建了一个DeferredResult对象,接着向业务线程池BIZ_POOL提交我们的请求处理逻辑(其内部处理完毕后把结果设置到创建的DeferredResult),最后返回创建的DeferredResult对象。其整个处理过程如下:
-
Tomcat容器接收路径为personDeferredResult的请求后,会分配一个容器线程来执行DispatcherServlet进行请求分派,请求被分到含有personDeferredResult路径的controller,然后执行listPostDeferredResult方法,该方法内创建了一个DeferredResult对象,然后把处理任务提交到了线程池进行处理,最后返回DeferredResult对象。
-
Spring MVC内部在personDeferredResult方法返回后会保存DeferredResult对象到内存队列或者列表,然后会调用request.startAsync()开启异步处理,并且调用DeferredResult对象的setResultHandler方法,设置当异步结果产生后对结果进行重新路由的回调函数(逻辑在WebAsyncManager的startDeferredResultProcessing方法)
-
最终在业务线程池中执行的异步任务会产生一个结果,该结果会被设置到DeferredResult对象,然后设置的回调函数会被调用,接着Spring MVC会分派请求结果回到Servlet容器继续完成处理,DispatcherServlet被再次调用,使用返回的异步结果继续进行处理,最终把响应结果写回请求方。
OK 今天关于DeferredResult的讲解就到这里 由于篇幅有限 具体的源码和原理我打算放到后面具体讲下,感兴趣的关注我。后面我们共同学习
与Callable区别
DeferredResult与Callable实现的功能类似,都是异步返回,只不过Callable不能直接设置超时时间,需要与FutureTask配合才行;DeferredResult可直接设置超时时间。
我以前写过一篇关于Callable 的文章 大家感兴趣的可以看下:
弦外之音
感谢你的阅读,如果你感觉学到了东西,麻烦您点赞,关注。也欢迎有问题我们下面评论交流
加油! 我们下期再见!
给大家分享几个我前面写的几篇骚操作