Web容器会为每个请求分配一个线程,默认情况下,响应完成前,该线程占用的资源都不会被释放。若有些请求需要长时间(例如长处理时间运算、等待某个资源),就会长时间占用线程所需资源,若这类请求很多,许多线程资源都被长时间占用,会对系统的性能造成负担。?
Servlet 3.0新增了异步处理,可以先释放容器分配给请求的线程与相关资源,减轻系统负担,原先释放了容器所分配线程的请求,其响应将被延后,可以在处理完成(例如长时间运算完成、所需资源已获得)时再对客户端进行响应。
Servlet 3.0 之前,一个普通 Servlet 的主要工作流程大致如下:
第一步,Servlet 接收到请求之后,可能需要对请求携带的数据进行一些预处理;
第二步,调用业务接口的某些方法,以完成业务处理;
第三步,根据处理的结果提交响应,Servlet 线程结束。
其中第二步的业务处理通常是最耗时的,这主要体现在数据库操作,以及其它的跨网络调用等,在此过程中,Servlet 线程一直处于阻塞状态,直到业务方法执行完毕。在处理业务的过程中,Servlet 资源一直被占用而得不到释放,对于并发较大的应用,这有可能造成性能的瓶颈。对此,在以前通常是采用私有解决方案来提前结束 Servlet 线程,并及时释放资源。
Servlet 3.0 针对这个问题做了开创性的工作,现在通过使用 Servlet 3.0 的异步处理支持,之前的 Servlet 处理流程可以调整为如下的过程:
第一步,Servlet 接收到请求之后,可能首先需要对请求携带的数据进行一些预处理;
第二步,Servlet 线程将请求转交给一个异步线程来执行业务处理,线程本身返回至容器,
第三步,Servlet 还没有生成响应数据,异步线程处理完业务以后,可以直接生成响应数据(异步线程拥有 ServletRequest 和 ServletResponse 对象的引用),或者将请求继续转发给其它 Servlet。
Servlet 线程不再是一直处于阻塞状态以等待业务逻辑的处理,而是启动异步线程之后可以立即返回。
?
请求入参ServletRequest对象新增了一个startAsync()方法。该方法用于开启异步,同时返回AsyncContext 异步上下文对象。
public AsyncContext startAsync() throws IllegalStateException;
可以通过AsyncContext的getRequest()、getResponse()方法取得请求、响应对象,此次对客户端的响应将暂缓至调用AsyncContext的complete()或dispatch()方法为止,前者表示响应完成,后者表示将调派指定的URL进行响应。
4、异步实现案例:
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>SpringBootStudy</artifactId>
<groupId>com.hsc.www</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>SB_33_servlet_app</artifactId>
<packaging>war</packaging>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.dreamlu</groupId>
<artifactId>mica-core</artifactId>
<version>2.1.1-GA</version>
</dependency>
</dependencies>
</project>
?web.xml
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
</web-app>
AsyncServlet.java:
package com.hsc.www.webFlux.servlet;
import net.dreamlu.mica.core.utils.$;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@WebServlet(name = "AsyncServlet", urlPatterns = {"/testAsyn"}, asyncSupported = true)
public class AsyncServlet extends GenericServlet {
ExecutorService executorService = Executors.newFixedThreadPool(10);
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws IOException {
AsyncContext asyncContext = servletRequest.startAsync();
executorService.submit(new Task(asyncContext));
PrintWriter out = asyncContext.getResponse().getWriter();
out.println("<h1>"+ $.formatDateTime(new Date())+" service threadName:" + Thread.currentThread().getName() + "</h1>");
out.flush();
}
public static class Task implements Runnable {
private final AsyncContext asyncContext;
public Task(AsyncContext asyncContext) {
this.asyncContext = asyncContext;
}
@Override
public void run() {
try {
HttpServletResponse response = (HttpServletResponse) asyncContext.getResponse();
sleep(10);
PrintWriter out = response.getWriter();
out.println("<h1>"+ $.formatDateTime(new Date())+" task threadName:" + Thread.currentThread().getName() + "</h1>");
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
asyncContext.complete();
}
}
}
private static void sleep(int s) {
try {
Thread.sleep(s * 1000L);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Tomcat servlet3.0需要 tomcat 7或更高版本才支持
运行结果: