异步servlet总结
- 支撑环境
服务端需要tomcat8.0及以上
jdk需要8及以上
- 配置选项
需要显式开启异步servlet配置,方法有两种:
- 在web.xml中对servlet配置的地方开启异步支持,如下所示
<servlet>
<servlet-name>DemoServlet</servlet-name>
<servlet-class>footmark.servlet.Demo Servlet</servlet-class>
<async-supported>true</async-supported>
</servlet>
- 在@WebServlet注解中开启异步支持,如下所示
@WebFilter(urlPatterns = "/demo",asyncSupported = true)
- 编码规范
- 在servlet中使用静态代码创建一个线程池,如下所示
static private ScheduledThreadPoolExecutor userExecutor =
new ScheduledThreadPoolExecutor(5);
- 新建异步事件处理类AsyncHandler,约定:
- 该类需要实现Runnable接口
- 该类需要一个传入的异步上下文环境
C、 在run方法中,从异步上下文环境中得到request和response,编码实现耗时的应用逻辑,之后通过respones将下行报文返回给客户端
public class AsyncHandler implements Runnable{
private AsyncContext ctx;
public AsyncHandler(AsyncContext ctx) {
this.ctx = ctx;
}
public void run() {
PrintWriter pw;
try {
pw = ctx.getResponse().getWriter();
pw.print("done");
pw.flush();
pw.close();
} catch(IOException e) {
e.printStackTrace();
}
ctx.complete();
}
}
- 在servlet处理请求的入口函数doPost、doGet、service中创建一个异步上下文环境,接着创建一个异步执行handler对象,向这个handler对象传入该异步上下文环境,最后在线程池上运行这个handler对象
AsyncContext aCtx = request.startAsync(request,response);
userExecutor.execute(new AsyncHandler(aCtx));
- 技术优势及原理
一个应用逻辑在tomcat中所需的处理时间分两块:等待进入tomcat内部线程池上运行的时间和应用的真实处理时间(实际上不止这两块时间,但此处为了方便分析做了简化)。
tomcat内部线程池的线程数量是有限的,如果应用的执行时间很长,就会长时间占用线程,使得线程不能很快地回到线程池。这样新的请求可能因为线程池中没有就绪的线程而不得不长时间的等待。这一方面会使得tomcat的并发数不能得到提升,另一方面使得用户体验不够好。
为了解决这个问题,可以在servlet内部新建一个内部线程池,当新的请求到来之后,会在tomcat内部的线程池中取出一个线程运行,但这个tomcat的线程并不真正处理那些耗时的业务逻辑,而是启动一个异步上下文,并将那些耗时的业务逻辑委托给servlet内部线程池去运行。tomcat的线程委托完之后自己就可以很快的返回tomcat的线程池中,以待新的请求到来。
servlet内部的线程池处理完耗时的业务请求之后,需要返回下行报文给客户端,所以它需要reqeust对象和resoponse对象,这就是为什么需要给异步handler传入异步上下文的原因---------在异步上下文中,可以得到request和response对象。
- 辨析
异步servlet技术并不能缩短应用逻辑处理的时间,这个时间无论如何是没法缩短的。然而,通过将应用逻辑的执行委托给servlet内部的线程池,tomcat的线程池就不需要自己亲自去执行这些耗时的逻辑了,因此tomcat的执行线程可以很快地返回自己的线程池中,这带来两个好处:tomcat可以处理更多的请求,换而言之就是提高了系统的并发量;新的请求可能无需等待就能及时被tomcat内部的线程池委托到servlet的内部线程池上,这个可能的等待时间被消灭了,换而言之就是从整体上减少了系统的响应时间。
但要说明的是,应用那些耗时的逻辑,无论是直接在tomcat内部线程池上运行,还是在servlet的内部现场池上运行,这个时间是无法缩短的。
源码
1、AsyncHandler.java
package com.nari;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.AsyncContext;
public class AsyncHandler implements Runnable{
private AsyncContext ctx;
public AsyncHandler(AsyncContext ctx) {
this.ctx = ctx;
}
@Override
public void run() {
PrintWriter pw;
try {
pw = ctx.getResponse().getWriter();
pw.print("done");
pw.flush();
pw.close();
} catch(IOException e) {
e.printStackTrace();
}
ctx.complete();
}
}
2.AsyncServlet.java
package com.nari;
import java.io.IOException;
import java.util.concurrent.ScheduledThreadPoolExecutor;
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;
@WebServlet(urlPatterns = "/AsyncServlet", asyncSupported = true)
public class AsyncServlet extends HttpServlet {
/**
*
*/
private static final long serialVersionUID = 1L;
static private ScheduledThreadPoolExecutor userExecutor = new ScheduledThreadPoolExecutor(5);
public AsyncServlet() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
AsyncContext aCtx = request.startAsync(request,response);
userExecutor.execute(new AsyncHandler(aCtx));
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}