這篇文章主要介紹“tomcat下使用Servlet異步模式的方法”,在日常操作中,相信很多人在tomcat下使用Servlet異步模式的方法問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”tomcat下使用Servlet異步模式的方法”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
在成都網(wǎng)站設(shè)計(jì)、做網(wǎng)站中從網(wǎng)站色彩、結(jié)構(gòu)布局、欄目設(shè)置、關(guān)鍵詞群組等細(xì)微處著手,突出企業(yè)的產(chǎn)品/服務(wù)/品牌,幫助企業(yè)鎖定精準(zhǔn)用戶,提高在線咨詢和轉(zhuǎn)化,使成都網(wǎng)站營銷成為有效果、有回報(bào)的無錫營銷推廣。創(chuàng)新互聯(lián)專業(yè)成都網(wǎng)站建設(shè)十載了,客戶滿意度97.8%,歡迎成都創(chuàng)新互聯(lián)客戶聯(lián)系。
servlet3.0版本以后,增加了對異步模式的支持。
以往在servlet里面,每一個新的請求到來都會由一個線程來接收處理,在處理過程中如果需要等待其他操作的結(jié)果,則線程就會處于阻塞狀態(tài)不能執(zhí)行其他任務(wù),待任務(wù)結(jié)束后該線程將結(jié)果輸出給客戶端,這時該線程才能繼續(xù)處理其他的請求。為了提高線程利用效率,servlet3.0版本以后增加了異步處理請求的模式,允許當(dāng)前線程將任務(wù)提交到給其他后臺線程處理(一般是后臺線程池,這樣只需要較少的線程就可以處理大量的任務(wù)),自身轉(zhuǎn)而去接收新的請求。
protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { response.getWriter().println("hello"); }
要使用異步模式,只需要調(diào)用request對象的startAsync方法即可,該方法返回一個AsyncContext對象供后續(xù)使用,可以通過該對象設(shè)置異步處理的超時間,添加異步處理的監(jiān)聽器等。然后將要處理的任務(wù)提交到某個線程池,當(dāng)前線程執(zhí)行完后續(xù)的代碼后就能去處理其他新的請求,不用等待當(dāng)前任務(wù)執(zhí)行完。當(dāng)前任務(wù)交由后臺線程池執(zhí)行完后,可以調(diào)用asyncContext.complete方法表示任務(wù)處理完成,觸發(fā)之前添加的監(jiān)聽器對事件進(jìn)行響應(yīng)。
protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { //啟用異步模式 final AsyncContext ac = request.startAsync(); //超時設(shè)置 ac.setTimeout(1000L); //添加監(jiān)聽器便于觀察發(fā)生的事件 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(); } } }); //方法結(jié)束當(dāng)前線程可以去處理其他請求了 }
由于asyncContext對象中持有請求中的request和response對象,所以在任務(wù)異步執(zhí)行完后仍然可以通過response將結(jié)果輸出給客戶端。但是,tomcat在經(jīng)過超時間之后還未收到complete消息,會認(rèn)為異步任務(wù)已經(jīng)超時,需要結(jié)束當(dāng)前的請求,從而將response對象放回對象池供其他請求繼續(xù)使用。這時response對象會分配給新的請求使用,按理就不應(yīng)該再被之前的異步任務(wù)共用!但是異步任務(wù)本身并不知道任務(wù)已經(jīng)超時了,還在繼續(xù)運(yùn)行,因此還會使用response對象進(jìn)行輸出,這時就會發(fā)生新的請求與后臺異步任務(wù)共同一個resonse對象的現(xiàn)象!這會造成多個線程向同一個客戶端輸出結(jié)果,將本不是該客戶端需要的結(jié)果輸出。試想一下:本來請求是的查詢我的訂單列表,結(jié)果收到了別人的訂單列表,這個后果是不是很嚴(yán)重呢?
為驗(yàn)證這個問題,可以使用以下代碼進(jìn)行測試:
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(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")); //這里只對第一次請求使用異步模式,后續(xù)請求均使用同步模式 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"); //超時設(shè)置為1s便于快速超時 ac.setTimeout(1000L); //添加監(jiān)聽器便于觀察發(fā)生的事件 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對象中輸出結(jié)果,便于客戶端觀察是否有收到該結(jié)果 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."); } }
在上面的測試示例中,我們對第一次請求開啟了異步模式,后續(xù)的請求仍然采用同步模式,并只是簡單地輸出response對象的hashcode,將一個任務(wù)提交到了線程池中運(yùn)行。在異步任務(wù)里每隔3s向客戶端輸出一次response對象的hashcode,而這個response對象是第一個請求的response對象,也就是說,它應(yīng)該與后續(xù)的請求使用了不同的response對象才對。但是在多次調(diào)用該servlet后,有些請求得到的結(jié)果中包含了第一次請求時產(chǎn)生的異步任務(wù)中輸出的內(nèi)容,也就是后續(xù)的有些請求與第一次請求共用了同一個response對象,tomcat對response對象進(jìn)行了重用!
測試結(jié)果如下:
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,所以上述請求有可能需要運(yùn)行多次才能出現(xiàn)預(yù)期的結(jié)果。
避坑方法:
異步任務(wù)如果需要使用response對象,先判斷當(dāng)前異步模式是否已經(jīng)超時和結(jié)束了,如果結(jié)束了則不要再使用該對象,使用request對象也是同理。不過,有時候我們會把request對象傳入異步任務(wù),在任務(wù)執(zhí)行的時候會從中取出一些數(shù)據(jù)使用,比如getParameter獲取參數(shù),這種情況下可以事先從request對象中獲取到異步任務(wù)需要的所有數(shù)據(jù),封裝成新的對象供異步任務(wù)使用,避免使用tomcat提供的request對象。
到此,關(guān)于“tomcat下使用Servlet異步模式的方法”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!