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(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对象。

servlet的坑_tomcat下使用Servlet异步模式的坑坑洼洼相关推荐

  1. Winsock的异步模式的I/O模型

    Winsock的异步模式的I/O模型 闲的没事看了下Winsock的异步模式的I/O模型,写些体会和感悟,记录一下. 1.Winsock同步阻塞方式的问题1 C0 l/ W8 {2 k 在异步非阻塞模 ...

  2. SpringBoot框架下使用Servlet

    SpringBoot框架下使用Servlet 创建一个Servlet继承HttpServlet 在web.xml配置文件中使用servlet servlet-mapping 1. 第一种方式:注解的方 ...

  3. servlet容器_Tomcat 容器与servlet的交互原理

    点击蓝字"程序员考拉"欢迎关注! Tomcat 是Web应用服务器,是一个Servlet/JSP容器. Tomcat 作为Servlet容器,负责处理客户请求,把请求传送给Serv ...

  4. Java EE WEB工程师培训-JDBC+Servlet+JSP整合开发之12.Servlet基础(2)

    –提交表单的方法 • get • post –Servlet 生命周期 –使用Servlet 输出HTML页面 –获得Servlet初始化参数 –页面导航 • 请求重定向 –response.send ...

  5. Struts2、SpringMVC、Servlet(Jsp)性能对比 测试 。 Servlet的性能应该是最好的,可以做为参考基准,其它测试都要向它看齐,参照...

    2019独角兽企业重金招聘Python工程师标准>>> Struts2.SpringMVC.Servlet(Jsp)性能对比 测试 . Servlet的性能应该是最好的,可以做为参考 ...

  6. MVC 模式/Servlet/JSP 编译原理剖析:Servlet 组件到底属于 MVC 模式的哪一层?

    文章目录 前言 一.回忆什么是 MVC 模式? 1.1.Model.View.Controller 组件介绍 1.2.明确 View 与 Controller 组件区别 二.什么是 Servlet? ...

  7. 在html中用表单插入servlet 怎么写servlet的地址,java web中servlet、jsp、html 互相访问的路径问题。...

    假设在myapp项目下有个login.html,index.jsp,还写了两个servletA和servletB. 在web.xml中的地址配置: /servlet/servletA /servlet ...

  8. java servlet类_[Java教程]与Servlet相关的类

    [Java教程]与Servlet相关的类 0 2017-08-31 17:00:15 有4个有关的类,通过servlet可以获得其中的三个,然后通过ServletConfig间接获取ServletCo ...

  9. 原生Servlet文件上传和下载Servlet多个文件上传

    2019独角兽企业重金招聘Python工程师标准>>> 转载:原文连接https://blog.csdn.net/HaHa_Sir/article/details/81744629 ...

最新文章

  1. error while loading shared libraries: libopencv_highgui.so.3.2: cannot open shared object file 的解决办法
  2. 安卓EditText
  3. 相离的圆(51Nod-1278)
  4. 玻璃质感_央美设计基础 | 造型基本功练习——玻璃质感训练
  5. Android上的APP图标常见尺寸规范
  6. Java时间 之 Instant
  7. 第53天-代码审计-TP5 框架及无框架变量覆盖反序列化(待续)
  8. 计算机如何连接iphone,iphone怎么连接到电脑的方法详解【图文】
  9. foo, bar ,baz
  10. 杭州学军中学信友队趣味网络邀请赛 总结
  11. android开发者mac(含M1芯片)电脑全新配置2022
  12. 工业4.0细谈MES制造执行系统
  13. 【位操作笔记】位反转算法 通过5 * lg(N)次运算完成
  14. 【逻辑漏洞技巧拓展】————3、逻辑漏洞之密码重置
  15. 防火墙SNMP服务器、日志服务器等配置(每天分享一些关于防火墙的基本配置,大家一起学习,一起交流下)
  16. 【19调剂】大连交通大学硕士研究生预调剂报名
  17. 夯实基础,Java8新特性Stream详细教程
  18. 利用C语言实现sin(x)曲线与cos(x)曲线图形的同时显示
  19. 塑胶模具设计-常用术语
  20. UG NX 曲面造型方法

热门文章

  1. 微信视频使用的是什么协议?—— udp协议的介绍
  2. leetcode--344. 反转字符串
  3. 配置ArchLinux系统
  4. 开放平台API接口加密,签名策略
  5. Effie 与文字讲缘分,与写作者交朋友
  6. 三、计算机网络的性能指标
  7. java/php/net/pythont羽毛球场地管理系统设计
  8. 【路径规划】RRT(Rapidly-exploring Random Trees)算法
  9. 山这边,山那边[泥巴网]
  10. 网站优化技术全解密(一)