web容器会为每个请求分配一个线程,Servlet3.0新增了异步处理,解决多个线程不释放占据内存的问题。可以先释放容器分配给请求的线程与相关资源,减轻系统负担,原先释放了容器所分配线程的请求,其响应将被延后,可以在处理完成后再对客户端进行响应。
一、AsyncContex简介
为了支持异步处理,在ServletRequest上提供了startAsync()方法。可以通过AsyncContext的getRequest()和getResponse()方法取得请求、响应对象,此次对客户端的响应将暂缓至调用AsyncContext的complete()或dispatch()方法为止。
首先要告知此容器支持Servlet异步处理,如:
1: @WebServlet(urlPatterns="/some.do", asyncSupported = true)
2: public class AsyncServlet extends HttpServlet{
3:
4: }
例1:异步处理的例子
AsyncServlet.java
1: package ServletAPI;
2:
3: import java.io.IOException;
4: import java.util.concurrent.ExecutorService;
5: import java.util.concurrent.Executors;
6: import javax.servlet.AsyncContext;
7: import javax.servlet.ServletException;
8: import javax.servlet.annotation.WebServlet;
9: import javax.servlet.http.HttpServlet;
10: import javax.servlet.http.HttpServletRequest;
11: import javax.servlet.http.HttpServletResponse;
12:
13: /**
14: * Servlet implementation class AsyncServlet
15: */
16: @WebServlet(name = "AsyncServlet", urlPatterns = { "/async.do" },asyncSupported=true)
17: public class AsyncServlet extends HttpServlet {
18: private static final long serialVersionUID = 1L;
19: private ExecutorService executorService=Executors.newFixedThreadPool(10);
20: /**
21: * @see HttpServlet#HttpServlet()
22: */
23: public AsyncServlet() {
24: super();
25: // TODO Auto-generated constructor stub
26: }
27:
28: /**
29: * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
30: */
31: protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
32: // TODO Auto-generated method stub
33: response.setContentType("text/html;charset=UTF-8");
34: AsyncContext ctx=request.startAsync();//开始异步处理,释放请求线程
35: executorService.submit(new AsynvRequest(ctx)); //创建AsyncRequest,调度线程
36:
37: }
38:
39: /**
40: * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
41: */
42: protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
43: // TODO Auto-generated method stub
44: }
45: public void destroy(){
46: executorService.shutdown();//关闭线程池
47: }
48:
49: }
50:
首先告诉容器,这个Servlet支持异步处理,对于每个请求,Servlet会取得其AsyncContext,并释放容器所分配的线程,响应被延迟后。对于这些被延迟后响应的请求,创建一个实现Runnable接口的AsyncRequest对象,并将其调度一个固定数量的线程池,让这些必须长时间处理的请求,在线程池中完成,不用每次分配线程。
例2:AsyncRequest是个实现Runnable的类,其模拟了长时间处理。
AsyncRequest.java
1: package ServletAPI;
2:
3: import java.io.PrintWriter;
4: import javax.servlet.AsyncContext;
5: public class AsynvRequest implements Runnable{
6: private AsyncContext ctx;
7:
8: public AsynvRequest(AsyncContext ctx) {
9: super();
10: this.ctx = ctx;
11: }
12:
13: @Override
14: public void run() {
15: // TODO Auto-generated method stub
16: try {
17: Thread.sleep(10000);//模拟冗长请求
18: PrintWriter out=ctx.getResponse().getWriter();
19: out.println("久等了...XD");//输出结果
20: ctx.complete();//对客户端完成响应
21: } catch (Exception e) {
22: // TODO Auto-generated catch block
23: throw new RuntimeException(e);
24: }
25:
26: }
27:
28: }
29:
以暂停线程的方式来模拟长时间处理,并输出简单的文字,最后调用complete()对客户端完成响应。
二、模拟服务器推播
HTTP是基于请求、响应模型,如果客户端要获得服务器的最新状态,就必须以定期方式发送请求,查询服务器端的最新状态。
Servlet 3.0提供的异步处理技术,可以解决每个请求占用线程的问题,再结合Ajax异步请求技术,就可以达到类似服务器主动通知浏览器的行为。这就是所谓的服务器端推播。
例3:模拟应用程序不定期产生最新数据,这个部分由实现ServletContextListener的类负责,会在程序启动时进行。
WebInitListener.java
1: package ServletAPI;
2:
3: import java.util.ArrayList;
4: import java.util.List;
5: import javax.servlet.AsyncContext;
6: import javax.servlet.ServletContextEvent;
7: import javax.servlet.ServletContextListener;
8: import javax.servlet.annotation.WebListener
9:
10: /**
11: * Application Lifecycle Listener implementation class WebInitListener
12: *
13: */
14: @WebListener
15: public class WebInitListener implements ServletContextListener {
16: private List<AsyncContext> asyncs=new ArrayList<>();//所有的异步请求AsyncContext将存储在这个List中。
17:
18: public void contextDestroyed(ServletContextEvent arg0) {
19: // TODO Auto-generated method stub
20: }
21:
22: /**
23: * @see ServletContextListener#contextInitialized(ServletContextEvent)
24: */
25: public void contextInitialized(ServletContextEvent arg0) {
26: // TODO Auto-generated method stub
27: new Thread(new Runnable(){
28: public void run(){
29: while(true){
30: try {//模拟产生随机数字
31: Thread.sleep((int)(Math.random()*10000));
32: double num=Math.random()*10;
33: synchronized (asyncs) {
34: for(AsyncContext ctx:asyncs){
35: ctx.getResponse().getWriter().println(num);
36: ctx.complete();
37: }
38: }
39: } catch (Exception e) {
40: // TODO Auto-generated catch block
41: throw new RuntimeException();
42: }
43: }
44: }
45: }).start();
46: }
47:
48: }
49:
有个List会存储所有的异步请求的AsyncContext,并在不定时产生数字后,逐一对客户端响应,并调用AsyncContext的conmplete()来完成请求。
负责接收请求的Servlet,一收到请求,就将之加入到List中。
AsyncNumServlet.java
1: package ServletAPI;
2:
3: import java.io.IOException;
4: import java.util.List;
5: import javax.servlet.AsyncContext;
6: import javax.servlet.ServletException;
7: import javax.servlet.annotation.WebServlet;
8: import javax.servlet.http.HttpServlet;
9: import javax.servlet.http.HttpServletRequest;
10: import javax.servlet.http.HttpServletResponse;
11:
12: @WebServlet(name = "AsyncNumServlet", urlPatterns = { "/asyncNum.do" }, asyncSupported=true)
13: public class AsyncNumServlet extends HttpServlet {
14: private static final long serialVersionUID = 1L;
15: private List<AsyncContext> asyncs;
16:
17: public void init() throws ServletException{
18: asyncs=(List<AsyncContext>)getServletContext().getAttribute("asyncs");
19:
20: }
21: /**
22: * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
23: */
24: protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
25: // TODO Auto-generated method stub
26: AsyncContext ctx=request.startAsync();//开始异步处理
27: synchronized (asyncs) {
28: asyncs.add(ctx);//加入维护AsyncContext的List中
29: }
30: }
31:
32: }
33:
由于List是储存为ServletContext属性,所以在Servlet中,必须从ServletContext中取出,每次请求到来时,调用HttpServletRequest的startAsync()进行异步处理,并取得AsyncContext加入维护AsyncContext的List中。
可以使用一个简单的HTML,使用Ajax技术,发送异步请求值服务器端,这个请求会被延迟,直到服务器端完成响应后,更新网页上的资料,并再度发送异步请求:
async.html
1: <!DOCTYPE html>
2: <html>
3: <head>
4: <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
5: <title>实时资料</title>
6: <script>
7: function asyncUpdate(){
8: var xhr;
9: if(window.XMLHttpRequest){
10: xhr=new XMLHttpRequest();
11: }else
12: if(window.ActiveXObject){
13: xhr=new ActiveXObject('Microsoft.XMLHTTP');
14: }
15: xhr.onreadystatechange=function(){
16: if(xhr.readyState==4){
17: if(xhr.status==200){
18: document.getElementById("data").innerHTML=xhr.responseText;
19: asyncUpdate();
20: }
21: }
22: };
23: xhr.open('GET','asyncNum.do?timestamp='+new Date().getTime());
24: xhr.send(null);
25: }
26: window.onload=asyncUpdate;
27: </script>
28: </head>
29: <body>
30: 实时资料:<span id="data">0</span>
31: </body>
32: </html>
可以试着用多个浏览器请求这个页面,会看到每个浏览器的资料是同步的。
当神已无能为力,那便是魔渡众生