springmvc webservlet 异步请求总结
1:每次请求会启动一个新线程
上边在debug状态下, 每次请求一次,生成一个新的 thread 在此已经是245了
出现一个现象在debug模式下, 每次请求生成的线程,自动在红框那个位置停了下来, 那个地方是没有设置断点的...... 每个线程都是如此.... 这个问题只能猜测,无法解释....程序解决了bug之后,还是这样,后期再观察...
后期发现:在此是程序出现bug 导致,线程运行到一半卡住,导致这个线程无法正常退出.异步线程启动后,任务执行完毕,此请求的线程任务完成,应该释放资源退出,留给后来者使用.
2: 这个在程序里边异步启动之后,比如加入了while循环之类的, 设置了时间较长的 timeout .如下文
AsyncContext asyncContext = request.startAsync();
asyncContext.setTimeout();
asyncContext.addListener(new AppAsyncListener());
Work work = new Work(asyncContext, request, packetSCnt);
new Thread(work).start();
在异步处理中新建一个异步context ,这个类把当前的response 封装了进去,并传递给work线程 .这个work线程去执行比如等待处理结果存入redis,这个work去轮询redis 取值
当取到值,如下while 循环
while (true) {
Thread.sleep();
// String mess= redis.get(devid+"02");
byte[] mess = redis.getByteFromList(...+ ""); //redis取值
if (mess != null) {
....... //省略
Gson gson = new Gson();
String gsonString = gson.toJson(rtCmdStat);
// String gsonString = null;
ServletResponse response = asyncContext.getResponse();
PrintWriter out = response.getWriter();
out.println(gsonString);
out.flush();
out.close();
// 保存命令
saveophistroy("tbox");
break;
}
在使用Servlet3.0的异步特性时,免不了会遇到下面这个异常
- java.lang.IllegalStateException: The request associated with the AsyncContext has already completed processing.
- at org.apache.catalina.core.AsyncContextImpl.check(AsyncContextImpl.java:521)
- at org.apache.catalina.core.AsyncContextImpl.getResponse(AsyncContextImpl.java:245)
具体为什么会产生呢,一直没想过。今天大概看了下代码,原因是这样的:
假设我们的Servlet代码如下,异步的逻辑在Executor中。
- AsyncContext ctx = req.startAsync();
- new Thread(new Executor(ctx)).start();
- class Executor implements Runnable {
- private AsyncContext ctx = null;
- public Executor(AsyncContext ctx){
- this.ctx = ctx;
- }
- public void run(){
- try {
- //等待三十秒钟,以模拟业务方法的执行
- System.out.println("进入线程处理时间:"+new Date());
- Thread.sleep(30000);
- PrintWriter out = ctx.getResponse().getWriter();//这一步一般会抛出异常
- System.out.println("结束线程处理时间");
- out.println("业务处理完毕的时间:" + new Date() + ".");
- out.flush();
- ctx.complete();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
此时,由于应用服务器(Tomcat)的异步超时时间默认为10秒,
- 此代码位于Request.java类中。会获取通道配置的异步超时时间
- asyncContext.setTimeout(getConnector().getAsyncTimeout());
而异步线程中会睡眠30秒,当30秒之后,会执行
- ctx.getResponse().getWriter()
这个ctx.getResponse()方法会判断此时的request是否为空,代码如下:
- @Override
- public ServletResponse getResponse() {
- check();
- return servletResponse;
- }
- private void check() {
- if (request == null) {
- // AsyncContext has been recycled and should not be being used
- throw new IllegalStateException(sm.getString(
- "asyncContextImpl.requestEnded"));//此处即为抛出的异常
- }
- }
所以,为了避免该问题,方法有二:
一,在异步Servlet的代码中执行的逻辑时间要小于配置的异步超时时间,
二,在应用服务器中将该时间增大
IllegalStateException 异常
IllegalStateException;
两个方法都返回 AsyncContext 实例, 第一个方法返回的 AsyncContext 包含原始的 request, response, 第二个方法带有ServletRequest var1, ServletResponse var2 参数, 可以传递原有实例, 也可以传递包装后的 wapper 实例.
注意, 重复调用 startAsync 方法只会返回相同 AsyncContext, 如果在不支持异步的 servlet 中调用 startAsync 方法会抛出IllegalStateException 异常,
注意, AsyncContext的 start 方法不会造成阻塞, 因此, 及时他派发的线程还没启动, 也会继续执行下一行代码.
编写异步 Servlet步骤
在 ServletRequest 中调用 startAsync 方法. startAsync 会返回一个 AsyncContext.
在 AsyncContext 中调用 setTimeout() 方法, 设置一个容器必须等待指定任务完成的毫秒数. 这个步骤是可选的, 但是如果没有社会这个时限, 将会采用容器的默认时间, 如果任务没能在规定实现内完成, 将会抛出异常.
调用 asyncContext.start 方法, 传递一个执行长时间的 Runnable.
任务完成时, 通过 Runnable 调用 AsyncContext.complete 方法 或 AsyncContext,dispatch方法表示任务完成, 终止这个线程, 若不调用, 会根据 setTimeout 的时间停止线程
---------------------
作者:ArtisticLife
来源:CSDN
原文:https://blog.csdn.net/cuanfuchu6411/article/details/79172409
版权声明:本文为博主原创文章,转载请附上博文链接!
所以必须加入asyncContext.complete() 结尾 ... 表示任务完成, 终止这个线程, 若不调用, 会根据 setTimeout 的时间停止线程 我的程序里边少了这个结束,每次请求明明取到了值,线程还要等到timeout时间结束,导致跑出异常......
附载一篇不错的文
Servlet 不是单例, 但在同一服务器的同一类请求中只会被创建一次.
Servlet 的实例创建个数与一下条件有关:
1.是否部署在分布式环境中, 非分布式只会创建一个实例
2.是否实现SingleThreadMode 接口, 若实现, 同一 Servlet 实例同一服务器最多创建20个
3.在 web.xml中被声明的次数, 若被声明多次, 则会被多次创建实例
Servlet容器默认是采用单实例多线程的方式处理多个请求的:
1.当web服务器启动的时候(或客户端发送请求到服务器时),Servlet就被加载并实例化(只存在一个Servlet实例);
2.容器初始化化Servlet主要就是读取配置文件(例如tomcat,可以通过servlet.xml的设置线程池中线程数目,初始化线程池通过web.xml,初始化每个参数值等等。
3.当请求到达时,Servlet容器通过调度线程(Dispatchaer Thread) 调度它管理下线程池中等待执行的线程(Worker Thread)给请求者;
4.线程执行Servlet的service方法;
5.请求结束,放回线程池,等待被调用;
(注意:避免使用实例变量(成员变量),因为如果存在成员变量,可能发生多线程同时访问该资源时,都来操作它,造成数据的不一致,因此产生线程安全问题)
同一 servlet 同时要处理多个请求, 就需要产生线程, 异步执行, 根据内存考虑, 在 Tomcat7 中, 处理进来请求的最多线程数量为200.
Servlet 或 Filter 一直占用请求处理线程, 直到它完成任务, 如果任务耗时, 并发用户的数量超出线程数量, 容器将会遇到超出线程的风险. Tomcat 会将超出的请求堆放在一个内部的服务器 Socket 中(其它容器可能不同). 如果继续进来更多请求, 会被拒绝访问, 知道有资源处理请求.
如果用户需要在请求时异步处理一个长时间任务, 但用户不必要知道任务执行的结果, 那么直接提供一个 Runnable 给 Executor, 并立即返回即可. 如果需要知道执行状态结果就需要做一些处理了.
一 .编写异步的 Servlet
WebServlet 和 WebFilter 注解类型可以包含 asyncSupport 属性, web.xml 的配置也有 配置, 为了编写支持异步处理的 Servlet 和 Filter ,这个属性必须为 true
支持异步处理的 Servlet 或 Filter 可以通过 ServletRequest 中调用 startAsync 方法来启动新的线程, z这个方法有两个重载的方法
AsyncContext startAsync() throws IllegalStateException;
AsyncContext startAsync(ServletRequest var1, ServletResponse var2) throws
1
2
3
IllegalStateException;
两个方法都返回 AsyncContext 实例, 第一个方法返回的 AsyncContext 包含原始的 request, response, 第二个方法带有ServletRequest var1, ServletResponse var2 参数, 可以传递原有实例, 也可以传递包装后的 wapper 实例.
注意, 重复调用 startAsync 方法只会返回相同 AsyncContext, 如果在不支持异步的 servlet 中调用 startAsync 方法会抛出IllegalStateException 异常,
注意, AsyncContext的 start 方法不会造成阻塞, 因此, 及时他派发的线程还没启动, 也会继续执行下一行代码.
编写异步 Servlet步骤
在 ServletRequest 中调用 startAsync 方法. startAsync 会返回一个 AsyncContext.
在 AsyncContext 中调用 setTimeout() 方法, 设置一个容器必须等待指定任务完成的毫秒数. 这个步骤是可选的, 但是如果没有社会这个时限, 将会采用容器的默认时间, 如果任务没能在规定实现内完成, 将会抛出异常.
调用 asyncContext.start 方法, 传递一个执行长时间的 Runnable.
任务完成时, 通过 Runnable 调用 AsyncContext.complete 方法 或 AsyncContext,dispatch方法表示任务完成, 终止这个线程, 若不调用, 会根据 setTimeout 的时间停止线程
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
AsyncContext asyncContext = req.startAsync();
asyncContext.setTimeout(5000);
req.setAttribute("mainThread", Thread.currentThread().getName());
asyncContext.start(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
req.setAttribute("asyncThread", Thread.currentThread().getName());
asyncContext.dispatch("/index.jsp");
}
});
// req.getRequestDispatcher("index.jsp").forward(req, resp);
}
调用asyncContext.dispatch(“/index.jsp”);可返回jsp 页面, 后面不需要写 // req.getRequestDispatcher(“index.jsp”).forward(req, resp);
如果用 req.getRequestDispatcher(“index.jsp”).forward(req, resp);req返回页面, 页面会重复获取 EL表达式内容, 并且无法获取到线程内存储的信息
如果不想在任务完成之时分配给两一个资源, 也可以在 AsyncContext中调用 complete 方法, 这个方法向 Servlet 容器表名, 任务已经完成
异步监听器
对于 Servlet 或 Filter 的异步操作, 有一个一步操作监听器 AsyncListener 接口, 这个接口有四个方法
void onComplete(AsyncEvent var1) throws IOException; //异步操作完成
void onTimeout(AsyncEvent var1) throws IOException; //异步操作超时
void onError(AsyncEvent var1) throws IOException; //异步操作失败
void onStartAsync(AsyncEvent var1) throws IOException; // 线程刚刚启动
AsyncListener 没有注解注册监听, 只能在 AsyncContext 中添加注册 listener 实例 如下
public class TestAsyncServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
AsyncContext asyncContext = req.startAsync();
asyncContext.setTimeout(5000);
asyncContext.addListener(new TestAsyncServletListener());
req.setAttribute("mainThread", Thread.currentThread().getName());
asyncContext.start(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
req.setAttribute("asyncThread", Thread.currentThread().getName());
asyncContext.dispatch("/index.jsp");
}
});
req.getRequestDispatcher("index.jsp").forward(req, resp);
}
}
public class TestAsyncServletListener implements AsyncListener {
@Override
public void onComplete(AsyncEvent asyncEvent) throws IOException {
System.out.println("完成");
}
@Override
public void onTimeout(AsyncEvent asyncEvent) throws IOException {
System.out.println("超时");
}
@Override
public void onError(AsyncEvent asyncEvent) throws IOException {
System.out.println("错误");
}
@Override
public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
System.out.println("开始");
}
}
其中要注意的是onStartAsync,由于AsyncContext是在ServletRequest的startSync后才能获取到,获取后才能添加listener,所以第一次的onStartAsync是不会被调用到的,但由于ServletRequest的startSync可以多次调用,所以当下次startSync时,onStartAsync才会被调用到。 (还没搞定, 还是不调用)
---------------------
作者:ArtisticLife
来源:CSDN
原文:https://blog.csdn.net/cuanfuchu6411/article/details/79172409
版权声明:本文为博主原创文章,转载请附上博文链接!
springmvc webservlet 异步请求总结的更多相关文章
- 使用Callable或DeferredResult实现springmvc的异步请求
使用Callable实现springmvc的异步请求 如果一个请求中的某些操作耗时很长,会一直占用线程.这样的请求多了,可能造成线程池被占满,新请求无法执行的情况.这时,可以考虑使用异步请求,即主线程 ...
- 15.SpringMVC之异步请求
SpringMVC中异步请求相关组件 SpringMVC在此基础上对异步请求进行了封装.提供了AsyncWebRequest类型的request,并提供了处理异步请求的管理器WebAsyncManag ...
- springmvc 配置异步请求
最开始按照网上配置了一个servlet class 没有继承Filter .结果报错.网上有文章说是tomcat 启动加载的servlet-3.0- api 加载了 tomcat 安装目录下lib里边 ...
- Springmvc中 同步/异步请求参数的传递以及数据的返回
转载:http://blog.csdn.net/qh_java/article/details/44802287 注意: 这里的返回就是返回到jsp页面 **** controller接收前台数据的方 ...
- eclipse Filter web.xml 问题解决 异步请求@WebServlet
<filter> <filter-name>AsynServlete</filter-name> <filter-class>com.kad.app.a ...
- springmvc中同步/异步请求参数的传递以及数据的返回
注意: 这里的返回就是返回到jsp页面 **** controller接收前台数据的方式,以及将处理后的model 传向前台***** 1.前台传递数据的接受:传的属性名和javabean的属性相同 ...
- 【坑】前端使用ajax异步请求以后,springMvc拦截器跳转页面无效
文章目录 前言 `$.ajaxSetup( )` 后记 前言 本文着重解决前后端分离开发的页面调整问题. 笔者,在做一个需求,需要对访问网站,但是没有登录的用户进行拦截,将他们重定向到首页. 很简单的 ...
- 天天写同步,5种SpringMvc异步请求了解下!
引言 说到异步大家肯定首先会先想到同步.我们先来看看什么是同步? 所谓同步,就是发出一个功能调用时,在没有得到结果之前,该调用就不返回或继续执行后续操作. 简单来说,同步就是必须一件一件事做,等前一件 ...
- Spring注解开发系列Ⅸ --- 异步请求
一. Servlet中的异步请求 在Servlet 3.0之前,Servlet采用Thread-Per-Request的方式处理请求,即每一次Http请求都由某一个线程从头到尾负责处理.如果要处理一些 ...
随机推荐
- Flink开发环境搭建(maven)
1.下载scala sdk http://www.scala-lang.org/download/ 直接到这里下载sdk,(https://downloads.lightbend.com/scala/ ...
- Eclipse 上传 删除 下载 分析 hdfs 上的文件
本篇讲解如何通过Eclipse 编写代码去操作分析hdfs 上的文件. 1.在eclipse 下新建Map/Reduce Project项目.如图: 项目建好后,会默认加载一系列相应的jar包. 下 ...
- Revit 命令添加下拉框
在学习revit制作下拉框时,需要分为三个步骤: 1.创建一个面板(panel). RibbonPanel panel10 = application.CreateRibbonPanel(Global ...
- Kafka(3)--kafka消息的存储及Partition副本原理
消息的存储原理: 消息的文件存储机制: 前面我们知道了一个 topic 的多个 partition 在物理磁盘上的保存路径,那么我们再来分析日志的存储方式.通过 [root@localhost ~]# ...
- java基础语法(标识符 修饰符 关键字)
标识符 用来表示类名,变量名,方法名,类型名,数组名,文件名的有效字符序列称为标识符. 1)只有字母(区分大小写),下划线,美元符号和数字组成,长度不受限制.注:字母包括英文26个字母 ,汉字,日 ...
- Slf4j与log4j及log4j2的关系及使用方法
Slf4j与log4j及log4j2的关系及使用方法 slf4j slf4j仅仅是一个为Java程序提供日志输出的统一接口,并不是一个具体的日志实现方案,就比如JDBC一样,只是一种规则而已,所以单独 ...
- ThreeJs 模型的缩放、移动、旋转 以及使用鼠标对三维物体的缩放
首先我们创建一个模型对象 var geometry = new THREE.BoxGeometry( 100, 100, 100); //边长100的正方体 var material = new TH ...
- win7 升级Power Shell到4.0
因为用到EntityFrameworkCore ,想使用scaffold 来生成models. 提示我power Shell 2.0不支持命令,然后需要升级PS. PS win7 升级文件下载地址是 ...
- React-Native: bios打开VT-x选项
问题: 我在Android Studio新建一个虚拟机的时候出现如图错误: 解决方案:重启电脑,开机的时候不停的按f12(不同的主机不一样),进入bios,然后打开Virtualization Tec ...
- Java框架spring Boot学习笔记(四):Spring Boot操作MySQL数据库
在pom.xml添加一下代码,添加操作MySQL的依赖jar包. <dependency> <groupId>org.springframework.boot</grou ...