(转)Servlet 3.0/3.1 中的异步处理学习
转:https://www.cnblogs.com/davenkin/p/async-servlet.html
在Servlet 3.0之前,Servlet采用Thread-Per-Request的方式处理请求,即每一次Http请求都由某一个线程从头到尾负责处理。如果一个请求需要进行IO操作,比如访问数据库、调用第三方服务接口等,那么其所对应的线程将同步地等待IO操作完成, 而IO操作是非常慢的,所以此时的线程并不能及时地释放回线程池以供后续使用,在并发量越来越大的情况下,这将带来严重的性能问题。即便是像Spring、Struts这样的高层框架也脱离不了这样的桎梏,因为他们都是建立在Servlet之上的。为了解决这样的问题,Servlet 3.0引入了异步处理,然后在Servlet 3.1中又引入了非阻塞IO来进一步增强异步处理的性能。
本文源代码:https://github.com/davenkin/servlet-3-async-learning
在Servlet 3.0中,我们可以从HttpServletRequest对象中获得一个AsyncContext对象,该对象构成了异步处理的上下文,Request和Response对象都可从中获取。AsyncContext可以从当前线程传给另外的线程,并在新的线程中完成对请求的处理并返回结果给客户端,初始线程便可以还回给容器线程池以处理更多的请求。如此,通过将请求从一个线程传给另一个线程处理的过程便构成了Servlet 3.0中的异步处理。
举个例子,对于一个需要完成长时处理的Servlet来说,其实现通常为:
package davenkin.servlet; import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException; @WebServlet("/syncHello")
public class SyncHelloServlet extends HttpServlet { protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
new LongRunningProcess().run();
response.getWriter().write("Hello World!");
}
}
为了模拟长时处理过程,我们创建了一个LongRunningProcess类,其run()方法将随机地等待2秒之内的一个时间:
package davenkin.servlet; import java.util.concurrent.ThreadLocalRandom; /**
* Created by yteng on 3/14/17.
*/
public class LongRunningProcess { public void run() {
try { int millis = ThreadLocalRandom.current().nextInt(2000);
String currentThread = Thread.currentThread().getName();
System.out.println(currentThread + " sleep for " + millis + " milliseconds.");
Thread.sleep(millis); } catch (InterruptedException e) {
e.printStackTrace();
}
}
}
此时的SyncHelloServlet将顺序地先执行LongRunningProcess的run()方法,然后将将HelloWorld返回给客户端,这是一个典型的同步过程。
在Servlet 3.0中,我们可以这么写来达到异步处理:
package davenkin.servlet; import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException; @WebServlet(value = "/simpleAsync", asyncSupported = true)
public class SimpleAsyncHelloServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
AsyncContext asyncContext = request.startAsync(); asyncContext.start(() -> {
new LongRunningProcess().run();
try {
asyncContext.getResponse().getWriter().write("Hello World!");
} catch (IOException e) {
e.printStackTrace();
}
asyncContext.complete();
}); } }
此时,我们先通过request.startAsync()获取到该请求对应的AsyncContext,然后调用AsyncContext的start()方法进行异步处理,处理完毕后需要调用complete()方法告知Servlet容器。start()方法会向Servlet容器另外申请一个新的线程(可以是从Servlet容器中已有的主线程池获取,也可以另外维护一个线程池,不同容器实现可能不一样),然后在这个新的线程中继续处理请求,而原先的线程将被回收到主线程池中。事实上,这种方式对性能的改进不大,因为如果新的线程和初始线程共享同一个线程池的话,相当于闲置下了一个线程,但同时又占用了另一个线程。
当然,除了调用AsyncContext的start()方法,我们还可以通过手动创建线程的方式来实现异步处理:
package davenkin.servlet; import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException; @WebServlet(value = "/newThreadAsync", asyncSupported = true)
public class NewThreadAsyncHelloServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { AsyncContext asyncContext = request.startAsync(); Runnable runnable = () -> {
new LongRunningProcess().run();
try {
asyncContext.getResponse().getWriter().write("Hello World!");
} catch (IOException e) {
e.printStackTrace();
}
asyncContext.complete();
}; new Thread(runnable).start(); } }
自己手动创建新线程一般是不被鼓励的,并且此时线程不能重用。因此,一种更好的办法是我们自己维护一个线程池。这个线程池不同于Servlet容器的主线程池,如下图:
在上图中,用户发起的请求首先交由Servlet容器主线程池中的线程处理,在该线程中,我们获取到AsyncContext,然后将其交给异步处理线程池。可以通过Java提供的Executor框架来创建线程池:
package davenkin.servlet; import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; @WebServlet(value = "/threadPoolAsync", asyncSupported = true)
public class ThreadPoolAsyncHelloServlet extends HttpServlet { private static ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 200, 50000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100)); protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { AsyncContext asyncContext = request.startAsync(); executor.execute(() -> { new LongRunningProcess().run(); try {
asyncContext.getResponse().getWriter().write("Hello World!");
} catch (IOException e) {
e.printStackTrace();
} asyncContext.complete(); });
} }
Servlet 3.0对请求的处理虽然是异步的,但是对InputStream和OutputStream的IO操作却依然是阻塞的,对于数据量大的请求体或者返回体,阻塞IO也将导致不必要的等待。因此在Servlet 3.1中引入了非阻塞IO(参考下图红框内容),通过在HttpServletRequest和HttpServletResponse中分别添加ReadListener和WriterListener方式,只有在IO数据满足一定条件时(比如数据准备好时),才进行后续的操作。
对应的代码示:
package davenkin.servlet; import javax.servlet.AsyncContext;
import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; @WebServlet(value = "/nonBlockingThreadPoolAsync", asyncSupported = true)
public class NonBlockingAsyncHelloServlet extends HttpServlet { private static ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 200, 50000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100)); protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { AsyncContext asyncContext = request.startAsync(); ServletInputStream inputStream = request.getInputStream(); inputStream.setReadListener(new ReadListener() {
@Override
public void onDataAvailable() throws IOException { } @Override
public void onAllDataRead() throws IOException {
executor.execute(() -> {
new LongRunningProcess().run(); try {
asyncContext.getResponse().getWriter().write("Hello World!");
} catch (IOException e) {
e.printStackTrace();
} asyncContext.complete(); });
} @Override
public void onError(Throwable t) {
asyncContext.complete();
}
}); } }
在上例中,我们为ServletInputStream添加了一个ReadListener,并在ReadListener的onAllDataRead()方法中完成了长时处理过程。
(转)Servlet 3.0/3.1 中的异步处理学习的更多相关文章
- Servlet 3.0/3.1 中的异步处理
在Servlet 3.0之前,Servlet采用Thread-Per-Request的方式处理请求,即每一次Http请求都由某一个线程从头到尾负责处理.如果一个请求需要进行IO操作,比如访问数据库.调 ...
- Spring 注解驱动(二)Servlet 3.0 注解驱动在 Spring MVC 中的应用
Spring 注解驱动(二)Servlet 3.0 注解驱动在 Spring MVC 中的应用 Spring 系列目录(https://www.cnblogs.com/binarylei/p/1019 ...
- 关于servlet3.0中的异步servlet
刚看了一下维基百科上的介绍,servlet3.0是2009年随着JavaEE6.0发布的: 到现在已经有六七年的时间了,在我第一次接触java的时候(2011年),servlet3.0就已经出现很久了 ...
- Servlet 3.0
Servlet 3.0 作为 Java EE 6 规范体系中一员,随着 Java EE 6 规范一起发布.该版本在前一版本(Servlet 2.5)的基础上提供了若干新特性用于简化 Web 应用的开发 ...
- 启用Servlet 3.0新特性——注解支持
Servlet 3.0版本新增注解支持,可是在实际使用中,添加的注解总是不起作用.经过检查,原来是“web.xml”文件的顶级标签“<web-app/>”中的一个叫做“metadata-c ...
- atitit.Servlet2.5 Servlet 3.0 新特性 jsp2.0 jsp2.1 jsp2.2新特性
atitit.Servlet2.5 Servlet 3.0 新特性 jsp2.0 jsp2.1 jsp2.2新特性 1.1. Servlet和JSP规范版本对应关系:1 1.2. Servlet2 ...
- [转]Servlet 3.0 新特性详解
原文地址:http://blog.csdn.net/xiazdong/article/details/7208316 Servlet 3.0 新特性概览 1.Servlet.Filter.Listen ...
- 关于 tomcat nio connector, servlet 3.0 async, spring mvc async 的关系
tomcat 的 org.apache.coyote.http11.Http11NioProtocol Connector 是一个使用 Java NIO 实现的异步 accept 请求的 connec ...
- atitit.j2ee 1.5 1.6 的不同跟 Servlet 3.0新特性总结
atitit.j2ee 1.5 1.6 的不同跟 Servlet 3.0新特性总结 1. jar比较,j2ee 1.6 添加了许多的jar 1 2. ,Servlet 3.0 2 2.1. 可插性 ...
随机推荐
- 修改属性item1(1变化)
给imgList1,7,12,16添加数据 数据层data:{imgList1:[],imgList7:[],imgList12:[],imgList16:[],} 处理层let _this=this ...
- Makefile之patsubst
经常要手写项目的Makefile,或者看其他项目的遗留项目的Makefile,有些makefile内置函数常用, 却用完就忘记了,最近项目中使用patsubst,感觉挺好用的 格式:$(pa ...
- EL表达式.jsp指令等
1.JSP标准指令:<%@ 标准指令(属性 )%><%@ page %><%@ include %><%@ taglib %> 2.JSP程序代码元素: ...
- [Python3] 006 列表的常用方法
目录 一个篱笆三个桩,list 有--好多个桩 1. 列表的小伙伴们 (1) 召唤小伙伴 (2) 我给"他们"分了个组 2. 小伙伴们的"才艺展示" (1) & ...
- vue 运行时报 dependency was not found:错误
这种报错我知道的有两种情况引起: 第一种: 是在引入文件的时候路径不对, 解决办法是: 只要在加一个./就行了: import test from './test' 改成先对路径 如果是安装的模块的话 ...
- [集合]List
List 存取有序,有索引,可以重复 ArrayList去除集合中字符串的重复值(字符串的内容相同) public static void main(String[] args) { ArrayLis ...
- luogu_P1177 【模板】快速排序 (快排和找第k大的数)
[算法] 选取pivot,然后每趟快排用双指针扫描(l,r)区间,交换左指针大于pivot的元素和右指针小于pivot的元素,将区间分成大于pivot和小于pivot的 [注意] 时间复杂度取决于pi ...
- EasyUi 表格自适应宽度
第一次接触EasyUi想要实现表格宽度自适应,网上找了好多文章,都没有实现,有网友实现了可是自己看不懂.可能是太简单高手都懒得分享,现在把自己的理解和实现记录一下,希望可以帮到向自己一样的菜鸟,步骤如 ...
- nodejs 模板引擎
自制替换模板 template.js var fs = require('fs') var http = require('http') var server = http.createServer( ...
- Firewalld--03 富规则、备份恢复、开启内部上网
目录 防火墙富规则.备份恢复.开启内部上网 1. 防火墙富规则策略 2.Firewalld备份恢复 3. 防火墙开启内部上网 防火墙富规则.备份恢复.开启内部上网 1. 防火墙富规则策略 Fire ...