您好,登錄后才能下訂單哦!
這篇文章主要介紹“tomcat下使用Servlet異步模式的方法”,在日常操作中,相信很多人在tomcat下使用Servlet異步模式的方法問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”tomcat下使用Servlet異步模式的方法”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
servlet3.0版本以后,增加了對異步模式的支持。
以往在servlet里面,每一個新的請求到來都會由一個線程來接收處理,在處理過程中如果需要等待其他操作的結果,則線程就會處于阻塞狀態不能執行其他任務,待任務結束后該線程將結果輸出給客戶端,這時該線程才能繼續處理其他的請求。為了提高線程利用效率,servlet3.0版本以后增加了異步處理請求的模式,允許當前線程將任務提交到給其他后臺線程處理(一般是后臺線程池,這樣只需要較少的線程就可以處理大量的任務),自身轉而去接收新的請求。
protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { response.getWriter().println("hello"); }
要使用異步模式,只需要調用request對象的startAsync方法即可,該方法返回一個AsyncContext對象供后續使用,可以通過該對象設置異步處理的超時間,添加異步處理的監聽器等。然后將要處理的任務提交到某個線程池,當前線程執行完后續的代碼后就能去處理其他新的請求,不用等待當前任務執行完。當前任務交由后臺線程池執行完后,可以調用asyncContext.complete方法表示任務處理完成,觸發之前添加的監聽器對事件進行響應。
protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { //啟用異步模式 final AsyncContext ac = request.startAsync(); //超時設置 ac.setTimeout(1000L); //添加監聽器便于觀察發生的事件 ac.addListener(new AsyncListener() { @Override public void onComplete(AsyncEvent asyncEvent) throws IOException { System.out.println("onComplete"); } @Override public void onTimeout(AsyncEvent asyncEvent) throws IOException { System.out.println("onTimeout"); } @Override public void onError(AsyncEvent asyncEvent) throws IOException { System.out.println("onError"); } @Override public void onStartAsync(AsyncEvent asyncEvent) throws IOException { System.out.println("onStartAsync"); } }); executor.submit(new Runnable() { @Override public void run() { //這里可以使用request, response,ac等對象 try { String user = request.getParameter("user"); response.getWriter().println("hello from async " + user); ac.complete(); } catch (IOException e) { e.printStackTrace(); } } }); //方法結束當前線程可以去處理其他請求了 }
由于asyncContext對象中持有請求中的request和response對象,所以在任務異步執行完后仍然可以通過response將結果輸出給客戶端。但是,tomcat在經過超時間之后還未收到complete消息,會認為異步任務已經超時,需要結束當前的請求,從而將response對象放回對象池供其他請求繼續使用。這時response對象會分配給新的請求使用,按理就不應該再被之前的異步任務共用!但是異步任務本身并不知道任務已經超時了,還在繼續運行,因此還會使用response對象進行輸出,這時就會發生新的請求與后臺異步任務共同一個resonse對象的現象!這會造成多個線程向同一個客戶端輸出結果,將本不是該客戶端需要的結果輸出。試想一下:本來請求是的查詢我的訂單列表,結果收到了別人的訂單列表,這個后果是不是很嚴重呢?
為驗證這個問題,可以使用以下代碼進行測試:
package async; import javax.servlet.AsyncContext; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; import javax.servlet.ServletException; 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; public class AsyncTimeoutServlet extends HttpServlet { boolean running = false; boolean stop = false; ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 50000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(100)); @Override public void init() throws ServletException { System.out.println("init AsyncTimeoutServlet"); } @Override public void destroy() { executor.shutdownNow(); } protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { stop = "true".equals(request.getParameter("stop")); //這里只對第一次請求使用異步模式,后續請求均使用同步模式 if (running) { System.out.println("running"); try { //在同步模式下輸出response對象的hashcode response.getWriter().println("this response belong's to you:" + response.toString()); } catch (IOException e) { System.out.println("response error"); } return; } running = true; //啟用異步模式 final AsyncContext ac = request.startAsync(); System.out.println("startAsync"); //超時設置為1s便于快速超時 ac.setTimeout(1000L); //添加監聽器便于觀察發生的事件 ac.addListener(new AsyncListener() { @Override public void onComplete(AsyncEvent asyncEvent) throws IOException { System.out.println("onComplete"); } @Override public void onTimeout(AsyncEvent asyncEvent) throws IOException { System.out.println("onTimeout"); } @Override public void onError(AsyncEvent asyncEvent) throws IOException { System.out.println("onError"); } @Override public void onStartAsync(AsyncEvent asyncEvent) throws IOException { System.out.println("onStartAsync"); } }); executor.submit(new Runnable() { @Override public void run() { while (!stop) { try { //每隔3s向原始的response對象中輸出結果,便于客戶端觀察是否有收到該結果 Thread.sleep(3000L); System.out.println("async run"); try { response.getWriter().println("if you see this message, something must be wrong. I'm " + response.toString()); } catch (IOException e) { System.out.println("async response error"); } } catch (InterruptedException e) { e.printStackTrace(); return; } } System.out.println("stop"); } }); System.out.println("ok, async mode started."); } }
在上面的測試示例中,我們對第一次請求開啟了異步模式,后續的請求仍然采用同步模式,并只是簡單地輸出response對象的hashcode,將一個任務提交到了線程池中運行。在異步任務里每隔3s向客戶端輸出一次response對象的hashcode,而這個response對象是第一個請求的response對象,也就是說,它應該與后續的請求使用了不同的response對象才對。但是在多次調用該servlet后,有些請求得到的結果中包含了第一次請求時產生的異步任務中輸出的內容,也就是后續的有些請求與第一次請求共用了同一個response對象,tomcat對response對象進行了重用!
測試結果如下:
curl -i "http://127.0.0.1:8080/servlet_async/async" HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Content-Length: 192 Date: Wed, 21 Aug 2019 07:55:26 GMT if you see this message, something must be wrong. I'm org.apache.catalina.connector.ResponseFacade@51582d92 this response belong's to you:org.apache.catalina.connector.ResponseFacade@51582d92
并不是每一次請求都能成功重用到同一個response,所以上述請求有可能需要運行多次才能出現預期的結果。
避坑方法:
異步任務如果需要使用response對象,先判斷當前異步模式是否已經超時和結束了,如果結束了則不要再使用該對象,使用request對象也是同理。不過,有時候我們會把request對象傳入異步任務,在任務執行的時候會從中取出一些數據使用,比如getParameter獲取參數,這種情況下可以事先從request對象中獲取到異步任務需要的所有數據,封裝成新的對象供異步任務使用,避免使用tomcat提供的request對象。
到此,關于“tomcat下使用Servlet異步模式的方法”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。