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 AsyncServlet15: */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 stub26: }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 stub33: 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 stub44: }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: @Override14: public void run() {15: // TODO Auto-generated method stub16: 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 block23: 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.WebListener9:10: /**11: * Application Lifecycle Listener implementation class WebInitListener12: *13: */14: @WebListener15: 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 stub20: }21:22: /**23: * @see ServletContextListener#contextInitialized(ServletContextEvent)24: */25: public void contextInitialized(ServletContextEvent arg0) {26: // TODO Auto-generated method stub27: 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 block41: 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 stub26: 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: }else12: 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>
可以試着用多個瀏覽器請求這個頁面,會看到每個瀏覽器的資料是同步的。
