Tomcat分配的servlet线程是非常消耗资源的,如果处理十分复杂的逻辑业务会导致系统分配servlet线程迟迟不能回归到tomcat线程队列之中,造成性能下降,异步servlet的主要目的是为了让tomcat分配的工作线程早点回收,而相应可以延后返回,因此采用异步servlet并没有加快响应的返回速度,只是让tomcat可以同时处理更多的请求。
按照传统的servlet的走法应该如下图所示
服务器启动时(web.xml中配置load-on-startup=1,默认为0)或者第一次请求该servlet时,就会初始化一个Servlet对象,也就是会执行初始化方法init(ServletConfig conf)
该servlet对象去处理所有客户端请求,在service(ServletRequest req,ServletResponse res)方法中执行
最后服务器关闭时,才会销毁这个servlet对象,执行destroy()方法。
所以我们可以知道Servlet容器从头到尾只有一个servlet单实例的对象。
Servlet容器默认是采用单实例多线程的方式处理多个请求的:
容器收到一个Servlet请求,调度线程从线程池中选出一个工作者线程,将请求传递给该工作者线程,然后由该线程来执行Servlet的service方法。当这个线程正在执行的时候,容器收到另外一个请求,调度线程同样从线程池中选出另一个工作者线程来服务新的请求,
容器并不关心这个请求是否访问的是同一个Servlet.当容器同时收到对同一个Servlet的多个请求的时候,那么这个Servlet的service()方法将在多线程中并发执行。
异步情况下的求情处理流程是这样的,首先还是会有请求到来,容器开线程来执行service方法,不过我们可以把业务逻辑另外开一个线程来解决,让容器分配的线程早点回到线程队列之中,我们的响应稍后在返回给客户端。
下面展示一下代码 抄自https://blog.csdn.net/wangxindong11/article/details/78591396
package com.test;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.ThreadPoolExecutor;
/**
* asyncSupported=true表示该servlet支持异步
*/
@WebServlet(urlPatterns = "/AsyncLongRunningServlet", asyncSupported = true)
public class AsyncLongRunningServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
long startTime = System.currentTimeMillis();
System.out.println("AsyncLongRunningServlet Start::Name="
+ Thread.currentThread().getName() + "::ID="
+ Thread.currentThread().getId());
AsyncContext asyncCtx = request.startAsync();
asyncCtx.addListener(new AppAsyncListener());
/*异步servlet的超时时间,异步Servlet有对应的超时时间,
* 如果在指定的时间内没有执行完操作,response依然会走原来Servlet的结束逻辑
*,后续的异步操作执行完再写回的时候,可能会遇到异常。*/
asyncCtx.setTimeout(9000);
ThreadPoolExecutor executor = (ThreadPoolExecutor) request
.getServletContext().getAttribute("executor");
executor.execute(new AsyncRequestProcessor(asyncCtx, 1000));
long endTime = System.currentTimeMillis();
System.out.println("AsyncLongRunningServlet End::Name="
+ Thread.currentThread().getName() + "::ID="
+ Thread.currentThread().getId() + "::Time Taken="
+ (endTime - startTime) + " ms.");
}
}
package com.test;
import javax.servlet.AsyncContext;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 业务工作线程
*/
public class AsyncRequestProcessor implements Runnable {
private AsyncContext asyncContext;
private int secs;
public AsyncRequestProcessor() {
}
public AsyncRequestProcessor(AsyncContext asyncCtx, int secs) {
this.asyncContext = asyncCtx;
this.secs = secs;
}
@Override
public void run() {
System.out.println("Async Supported? "
+ asyncContext.getRequest().isAsyncSupported());
try {
PrintWriter out = asyncContext.getResponse().getWriter();
Thread.sleep(secs);
out.write("Processing done for " + secs + " milliseconds!!");
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
asyncContext.complete();
}
}
package com.test;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Created by wangxindong on 2017/10/19.
* 在监听中初始化线程池
*/
@WebListener
public class AppContextListener implements ServletContextListener {
/**
* 当Servlet 容器启动Web 应用时调用该方法。在调用完该方法之后,容器再对Filter 初始化,
* 并且对那些在Web 应用启动时就需要被初始化的Servlet 进行初始化。
*/
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
// create the thread pool
ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 200, 50000L,
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(100));
servletContextEvent.getServletContext().setAttribute("executor",
executor);
}
/**
* 当Servlet 容器终止Web 应用时调用该方法。在调用该方法之前,容器会先销毁所有的Servlet 和Filter 过滤器。
*/
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
ThreadPoolExecutor executor = (ThreadPoolExecutor) servletContextEvent
.getServletContext().getAttribute("executor");
executor.shutdown();
}
}
package com.test;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebListener;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 1. 异步线程开始时,调用AsyncListener的onStartAsync(AsyncEvent event)方法;
* 2. 异步线程出错时,调用AsyncListener的onError(AsyncEvent event)方法;
* 3. 异步线程执行超时,则调用AsyncListener的onTimeout(AsyncEvent event)方法;
* 4. 异步执行完毕时,调用AsyncListener的onComplete(AsyncEvent event)方法;
*/
@WebListener
public class AppAsyncListener implements AsyncListener {
//异步执行完毕时,调用AsyncListener的onComplete(AsyncEvent event)方法
@Override
public void onComplete(AsyncEvent asyncEvent) throws IOException {
System.out.println("AppAsyncListener onComplete");
// we can do resource cleanup activity here
}
@Override
public void onError(AsyncEvent asyncEvent) throws IOException {
System.out.println("AppAsyncListener onError");
//we can return error response to client
}
@Override
public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
System.out.println("AppAsyncListener onStartAsync");
//we can log the event here
}
@Override
public void onTimeout(AsyncEvent asyncEvent) throws IOException {
System.out.println("AppAsyncListener onTimeout");
//we can send appropriate response to client
ServletResponse response = asyncEvent.getAsyncContext().getResponse();
PrintWriter out = response.getWriter();
out.write("TimeOut Error in Processing");
}
}
上面的代码就是模拟了一下业务时间较长的情况下的返回响应过程。我们在自己写的线程之中睡眠了一会儿。写了两个监听器看看什么情况。
发送请求我们也确实看到了响应。控制台也有了输出
那这样就确定了 AsyncContext 对象之中包含了这次请求和响应对象。并且工作线程在7ms的时间内回归到了队列中。
我们发现所谓的asyncContext实际作用就是传递了response那我们直接利用service中的response开启线程是否能行。
package com.test;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class RequestProcessor implements Runnable {
private HttpServletResponse response;
public RequestProcessor(HttpServletResponse response) {
this.response = response;
}
@Override
public void run() {
PrintWriter out = null;
try {
out = response.getWriter();
Thread.sleep(1000);
out.write("Processing done for " + 1000 + " milliseconds!!");
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在servlet中注释掉和异步有关的代码,然后启动我们的这个线程
executor.execute(new RequestProcessor(response));
我们发现是connector组件出错了。网上查了一下说是封装出错了。
接下来我又尝试了同步情况下将response放在其他类里面写是没有任何问题的。看来问题就在于线程之间的变量传递上,因为再往response流里面写入一些字符时,工作线程已经返回队列了,因此会出现问题。
具体的源码和执行流程我百度也没找到,看了下AsyncContext的源代码,发现有个内部类
以下纯属推测,并没有验证,以后会完善。
private static class AsyncRunnable implements Runnable {
private final AsyncDispatcher applicationDispatcher;
private final Request request;
private final ServletRequest servletRequest;
private final ServletResponse servletResponse;
public AsyncRunnable(Request request, AsyncDispatcher applicationDispatcher, ServletRequest servletRequest, ServletResponse servletResponse) {
this.request = request;
this.applicationDispatcher = applicationDispatcher;
this.servletRequest = servletRequest;
this.servletResponse = servletResponse;
}
public void run() {
this.request.getCoyoteRequest().action(ActionCode.ASYNC_DISPATCHED, (Object)null);
try {
this.applicationDispatcher.dispatch(this.servletRequest, this.servletResponse);
} catch (Exception var2) {
throw new RuntimeException(var2);
}
}
}
private static class RunnableWrapper implements Runnable {
private final Runnable wrapped;
private final Context context;
private final org.apache.coyote.Request coyoteRequest;
public RunnableWrapper(Runnable wrapped, Context ctxt, org.apache.coyote.Request coyoteRequest) {
this.wrapped = wrapped;
this.context = ctxt;
this.coyoteRequest = coyoteRequest;
}
public void run() {
ClassLoader oldCL = this.context.bind(Globals.IS_SECURITY_ENABLED, (ClassLoader)null);
try {
this.wrapped.run();
} finally {
this.context.unbind(Globals.IS_SECURITY_ENABLED, oldCL);
}
this.coyoteRequest.action(ActionCode.DISPATCH_EXECUTE, (Object)null);
}
}
从这个线程类推测出我们的异步servlet响应是由tomcat容器中的线程池在继续完成的。所以在不同的线程之间传递request和response还是需要用到servlet提供的AsyncContext类来完成的。自己开线程会导致让tomcat工作线程无法识别这个响应,最后导致connector的封装失败。
当然这都是推测,以后会仔细研究一下,如果有错误请您指出,感谢您的阅读。