有的時候servlet在相應報文以前會有一些耗時操做,好比JDBC的操做,或者等待另外一個遠程Web的響應,同步Servlet中等待阻塞會致使Web容器總體的處理能力低下。對於這種狀況可以使用servlet異步處理方式,把比較耗時的操做能夠放置到另一個線程中進行處理,此過程保留鏈接的請求和響應對象,在處理完成以後能夠把處理的結果通知到客戶端。java
如圖所示,Tomcat的客戶端請求由管道處理最後會經過Wrapper容器的管道,這時它會調Servlet實例的service方法進行邏輯處理,處理完後響應客戶端,整個處理由Tomcat的Executor線程池的線程處理。apache
![img](file:///var/folders/74/8gxv5r0j3xvflks6fxth63540000gn/T/WizNote/de95c2ac-493a-4c0b-ac87-f45447bb6345/index_files/73010586.png)tomcat
缺點:碰見處理邏輯耗時較長的任務會長時間的佔用tomcat的處理線程,影響tomcat的總體處理能力。特別是當你的線程數有限好比只有5個,同時碰見5個耗時100ms的請求,那麼在這100ms內的其它請求都會失敗,嚴重影響的服務的吞吐量和穩定性。app
優勢:實現邏輯簡單易懂,對於大量的耗時短的任務性能好。異步
異步Servlet是在3.0版本引入的,客戶端請求到來,而後經過管道最後進入到Wrapper容器的管道,調用Servlet實例的service後,建立一個異步上下文將耗時的邏輯操做封裝起來,交給用戶本身定義的線程池,這時Tomcat的處理線程就能立刻回到Executor線程池,而不用等待耗時的操做完成才讓出線程,從而提高了Tomcat的總體處理能力。async
![img](file:///var/folders/74/8gxv5r0j3xvflks6fxth63540000gn/T/WizNote/de95c2ac-493a-4c0b-ac87-f45447bb6345/index_files/73445233.png)ide
初始化線程池性能
@WebListener
public class AppContextListener implements ServletContextListener {
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);
}
public void contextDestroyed(ServletContextEvent servletContextEvent) {
ThreadPoolExecutor executor = (ThreadPoolExecutor) servletContextEvent
.getServletContext().getAttribute("executor");
executor.shutdown();
}
}
複製代碼
編寫一個監聽器,監聽異步任務生命週期中的一些動做。this
public class AppAsyncListener implements AsyncListener {
@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");
}
}
複製代碼
具體的業務邏輯放在一個Runable的實現類中url
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());
longProcessing(secs);
try {
PrintWriter out = asyncContext.getResponse().getWriter();
out.write("Processing done for " + secs + " milliseconds!!");
} catch (IOException e) {
e.printStackTrace();
}
//complete the processing
asyncContext.complete();
}
private void longProcessing(int secs) {
// wait for given time before finishing
try {
Thread.sleep(secs);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
複製代碼
最後,Async 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());
request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);
String time = request.getParameter("time");
int secs = Integer.valueOf(time);
// max 10 seconds
if (secs > 10000) {
secs = 10000;
}
AsyncContext asyncCtx = request.startAsync();
asyncCtx.addListener(new AppAsyncListener());
asyncCtx.setTimeout(9000);
ThreadPoolExecutor executor = (ThreadPoolExecutor) request
.getServletContext().getAttribute("executor");
executor.execute(new AsyncRequestProcessor(asyncCtx));
long endTime = System.currentTimeMillis();
System.out.println("AsyncLongRunningServlet End::Name="
+ Thread.currentThread().getName() + "::ID="
+ Thread.currentThread().getId() + "::Time Taken="
+ (endTime - startTime) + " ms.");
}
}
複製代碼
PS:在 SpringBootApplication 上使用@ServletComponentScan 註解後,Servlet、Filter、Listener 能夠直接經過 @WebServlet、@WebFilter、@WebListener 註解自動註冊,無需其餘代碼。