每个请求来到Web容器,Web容器会为其分配一个线程来专门负责该请求,直到完成处理前,该执行线程都不会被释放回容器。 执行线程会耗用系统资源,若有些请求需要长时间处理(例如长时间运算、等待某个资源),就会长时间占用执行线程。
若这类的请求很多,许多执行线程都被长时间占用,而在web容器内,可以使用的线程都是有限的,这对于系统就会是个负担,甚至造成应用程式的性能瓶颈。
基本上一些需长时间处理的请求,通常客户端也较不在乎请求后要有立即的回应。若可以,让这类请求先释放容器分配给该请求的执行线程,让容器可以有机会将执行线程资源分配给其它的请求,可以减轻系统负担。
很多项目在遇到需长时间处理的任务时,经常启一个新线程或者扔到线程池中,这样不耽误任务主线的流程,这个servlet的asynch特性其实发挥同样的作用
释放了容器所分配执行线程的请求,其回应将被延后,直到处理完成(例如长时间运算完成、所需资源已获得)再行对客户端的回应, 如果超过浏览器的链接时长,会将servlet中的内容返回,而asyncContext中的内容则不能返回,服务器抛出java.lang.IllegalStateException错误
在Servlet 3.0中,提供了AsyncContext,对异步执行的上下文提供支持。在ServletRequest上提供了 startAsync( )方法,用于启动异步工作线程。而且AsynchContext还提供了Timeout等设置。
你可以透过AsyncContext的getRequest() 、 getResponse()方法取得Request、Response对象,此次对客户端的响应将暂缓至调用AsyncContext的complete()方法或dispatch()为止,前者表示回应完成,后者表示将响应调派给指定的URL 。
若要能调用ServletRequest的startAsync()使用AsyncContext,则此Servlet 必须能支援非同步处理,如果使用@WebServlet来标注,则可以设定其asyncSupported为true 。 例如:
@WebServlet(urlPatterns = "/some.do", asyncSupported = true )
public class AsyncServlet extends HttpServlet
...
如果使用web.xml设定Servlet,则可以设定标签为true :
...
AsyncServlet
com.pkgname.AsyncServlet
true
...
如果Servlet将会异步处理,若其前端有过滤器,则过滤器亦需标示其支援异步处理,如果使用@WebFilter ,同样是可以设定其asyncSupported为true 。 例如:
@WebFilter(urlPatterns = "/some.do", asyncSupported = true )
public class AsyncFilter implements Filter{
...
如果使用web.xml设定过滤器,则可以设定标签为true :
...
< filter -name>AsyncFilter filter -name>
< filter -class>com.pkgname.AsyncFilter filter -class>
true
filter >
...
注意:
使用异步处理方式,web容器的请求处理线程释放了,可以服务其他的请求处理。但是该Request的处理并没有结束,在使用AsyncContext的complete或者dispatch完成后,这个request的处理才结束。
简单的示例:
package com.ss;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
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 com.cndatacom.thread.BusinessHandleThread;
/**
* 异步处理Servlet
* @author
*/
/**
* asyncSupported属性默认是false,如果需要开启支持异步处理功能,需要设置为true
*/
@WebServlet(name = "AsyncServlet", urlPatterns = "/AsyncServlet2", asyncSupported = true)
public class AsyncServlet2 extends HttpServlet {
/**
*
*/
private static final long serialVersionUID = -2749650094193187229L;
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
response.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
Date date = new Date(System.currentTimeMillis());
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss Z");
out.println("Servlet begin --" + sdf.format(date) + "
");// 响应输出到客户端
// 进入异步模式,调用业务处理线程进行业务处理
// Servlet不会被阻塞,而是直接往下执行
// 业务处理完成后的回应由AsyncContext管理
AsyncContext asyncContext = request.startAsync();
BusinessWorkerThread businessHandleThread = new BusinessWorkerThread(
asyncContext);
Thread thread = new Thread(businessHandleThread);
thread.start();
//asyncContext.start(businessHandleThread);//也可以用这种方法启动异步线程
date = new Date(System.currentTimeMillis());
out.println("Servlet end --" + sdf.format(date) + "
");
out.flush();
}
}
------
package com.ss;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.servlet.AsyncContext;
import javax.servlet.ServletResponse;
/**
* 业务处理线程
*
* @author
*/
public class BusinessWorkerThread implements Runnable {
// 异步操作的上下文对象,通过构造方法传进来
private AsyncContext asyncContext;
public BusinessWorkerThread(AsyncContext asyncContext) {
this.asyncContext = asyncContext;
}
@Override
public void run() {
try {
// do some work...
Thread.sleep(8000); // 和browser的timeout时间相关。如果browser上timeout是30s,则大于30时,网络已经断开。这是就会异常。
ServletResponse response = asyncContext.getResponse();
PrintWriter out = response.getWriter();
Date date = new Date(System.currentTimeMillis());
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss Z");
out.println("business worker finished --"+ sdf.format(date));// 响应输出到客户端
// 告诉启动异步处理的Servlet异步处理已完成,Servlet就会提交请求响应
asyncContext.complete();
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出: 在浏览器中,输出的内容按照后面的时间显示在页面上。
Servlet begin --22:33:57 +0800
Servlet end --22:33:57 +0800
business worker finished --22:34:05 +0800
注意: 使用浏览器访问,因为各个浏览器的http请求超时设置不同,比如我的chrome是30秒。AsyncContext中对应的工作线程的持续时间需要小于浏览器的http超时时间。
AsyncContext还可以设置一个Listener,对4个事件进行处理:
示例:
package com.ss;
import java.io.IOException;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class AsyncServletTest
*/
@WebServlet(asyncSupported = true, urlPatterns = { "/AsyncTest" })
public class AsyncServletTest extends HttpServlet {
String param = "";
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public AsyncServletTest() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
* response)
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 1 start async
final AsyncContext ctx = req.startAsync();
param = ctx.getRequest().getParameter("seq");
System.out.println("getRequest request seq: " + param);
// 2 set the timeout
ctx.setTimeout(0);
// 3 add listener
ctx.addListener(new AsyncListener() {
@Override
public void onTimeout(AsyncEvent arg0) throws IOException {
System.out.println("onTimeout...");
}
@Override
public void onStartAsync(AsyncEvent arg0) throws IOException {
System.out.println("onStartAsync...");
}
@Override
public void onError(AsyncEvent arg0) throws IOException {
System.out.println("onError...");
}
@Override
public void onComplete(AsyncEvent arg0) throws IOException {
System.out.println("onComplete...");
}
});
// 4 run a thread
ctx.start(new Runnable() {
@Override
public void run() {
String seq = ctx.getRequest().getParameter("seq");
System.out.println(">>>>>now respone: " + seq);
int n = 0;
try {
// hold until receive exit
while (!param.equals("exit")) {
n++;
if (n % 100000000 == 0) {
System.out.println(seq + ": ..." + n);
//
}
}
ctx.getResponse().getWriter().write(seq+" -- "+n);
} catch (IOException e) {
e.printStackTrace();
}
ctx.complete();
}
});
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
System.out.println("doPost...");
}
}