这篇文章主要介绍“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进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。