springmvc webservlet 异步请求总结

1:每次请求会启动一个新线程

上边在debug状态下, 每次请求一次,生成一个新的 thread  在此已经是245了

出现一个现象在debug模式下, 每次请求生成的线程,自动在红框那个位置停了下来, 那个地方是没有设置断点的...... 每个线程都是如此.... 这个问题只能猜测,无法解释....程序解决了bug之后,还是这样,后期再观察...

后期发现:在此是程序出现bug 导致,线程运行到一半卡住,导致这个线程无法正常退出.异步线程启动后,任务执行完毕,此请求的线程任务完成,应该释放资源退出,留给后来者使用.

2: 这个在程序里边异步启动之后,比如加入了while循环之类的, 设置了时间较长的 timeout .如下文

         AsyncContext asyncContext = request.startAsync();
            asyncContext.setTimeout(6000);
            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(100);
                        // String mess= redis.get(devid+"02");
                        byte[] mess = redis.getByteFromList(...+ "02"); //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代码  收藏代码
  1. java.lang.IllegalStateException: The request associated with the AsyncContext has already completed processing.  
  2.      at org.apache.catalina.core.AsyncContextImpl.check(AsyncContextImpl.java:521)  
  3.      at org.apache.catalina.core.AsyncContextImpl.getResponse(AsyncContextImpl.java:245)  


具体为什么会产生呢,一直没想过。今天大概看了下代码,原因是这样的: 
假设我们的Servlet代码如下,异步的逻辑在Executor中。 

Java代码  收藏代码
  1. AsyncContext ctx = req.startAsync();  
  2.        new Thread(new Executor(ctx)).start();         
  3.           
  4.           
  5.  class Executor implements Runnable {  
  6.     private AsyncContext ctx = null;  
  7.     public Executor(AsyncContext ctx){  
  8.         this.ctx = ctx;  
  9.     }  
  10.   
  11.     public void run(){  
  12.         try {  
  13.             //等待三十秒钟,以模拟业务方法的执行  
  14.              System.out.println("进入线程处理时间:"+new Date());  
  15.             Thread.sleep(30000);  
  16.             PrintWriter out = ctx.getResponse().getWriter();//这一步一般会抛出异常  
  17.             System.out.println("结束线程处理时间");  
  18.             out.println("业务处理完毕的时间:" + new Date() + ".");  
  19.             out.flush();  
  20.             ctx.complete();  
  21.         } catch (Exception e) {  
  22.             e.printStackTrace();  
  23.         }  
  24.     }         


此时,由于应用服务器(Tomcat)的异步超时时间默认为10秒, 

Java代码  收藏代码
  1. 此代码位于Request.java类中。会获取通道配置的异步超时时间  
  2. asyncContext.setTimeout(getConnector().getAsyncTimeout());  


而异步线程中会睡眠30秒,当30秒之后,会执行 

Java代码  收藏代码
  1. ctx.getResponse().getWriter()  


这个ctx.getResponse()方法会判断此时的request是否为空,代码如下: 

Java代码  收藏代码
  1.  @Override  
  2. public ServletResponse getResponse() {  
  3.     check();  
  4.     return servletResponse;  
  5. }  
  6.   
  7.  private void check() {  
  8.     if (request == null) {  
  9.         // AsyncContext has been recycled and should not be being used  
  10.         throw new IllegalStateException(sm.getString(  
  11.                 "asyncContextImpl.requestEnded"));//此处即为抛出的异常  
  12.     }  
  13. }  



所以,为了避免该问题,方法有二: 
一,在异步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
版权声明:本文为博主原创文章,转载请附上博文链接!

原文地址:https://www.cnblogs.com/zuochanzi/p/10081632.html