【1】HelloAsyncServlet
在Servlet 3.0之前,Servlet采用Thread-Per-Request
的方式处理请求,即每一次Http请求都由某一个线程从头到尾负责处理。
如果一个请求需要进行IO操作,比如访问数据库、调用第三方服务接口等,那么其所对应的线程将同步地等待IO操作完成, 而IO操作是非常慢的,所以此时的线程并不能及时地释放回线程池以供后续使用,在并发量越来越大的情况下,这将带来严重的性能问题。
为了解决这样的问题,Servlet 3.0引入了异步处理,然后在Servlet 3.1中又引入了非阻塞IO来进一步增强异步处理的性能。
实例代码
asyncSupported=true
开启异常支持,默认为false
@WebServlet(value="/async",asyncSupported=true) public class HelloAsyncServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //1、支持异步处理asyncSupported=true //2、开启异步模式 System.out.println("主线程开始。。。"+Thread.currentThread()+"==>"+System.currentTimeMillis()); AsyncContext startAsync = req.startAsync(); //3、业务逻辑进行异步处理;开始异步处理 startAsync.start(new Runnable() { @Override public void run() { try { System.out.println("副线程开始。。。"+Thread.currentThread()+"==>"+System.currentTimeMillis()); sayHello(); //获取到异步上下文 AsyncContext asyncContext = req.getAsyncContext(); //4、获取响应 ServletResponse response = asyncContext.getResponse(); response.getWriter().write("hello async..."); System.out.println("副线程结束。。。"+Thread.currentThread()+"==>"+System.currentTimeMillis()); startAsync.complete(); } catch (Exception e) { } } }); System.out.println("主线程结束。。。"+Thread.currentThread()+"==>"+System.currentTimeMillis()); } public void sayHello() throws Exception{ System.out.println(Thread.currentThread()+" processing..."); Thread.sleep(3000); } }
测试结果如下:
主线程开始。。。Thread[http-apr-8080-exec-7,5,main]==>1524209187508 主线程结束。。。Thread[http-apr-8080-exec-7,5,main]==>1524209187560 副线程开始。。。Thread[http-apr-8080-exec-9,5,main]==>1524209187560 Thread[http-apr-8080-exec-9,5,main] processing... asyncContext..org.apache.catalina.core.AsyncContextImpl@e9f2202 副线程结束。。。Thread[http-apr-8080-exec-9,5,main]==>152420919900
【2】ThreadPoolAsyncHelloServlet
自己手动创建新线程一般是不被鼓励的,并且此时线程不能重用。因此,一种更好的办法是我们自己维护一个线程池。这个线程池不同于Servlet容器的主线程池,如下图:
实例代码如下:
@WebServlet(value = "/threadPoolAsync", asyncSupported = true) public class ThreadPoolAsyncHelloServlet extends HttpServlet { private static final long serialVersionUID = 1L; private static ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 200, 50000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100)); protected void doGet(HttpServletRequest request, HttpServletResponse response) { AsyncContext startAsync = request.startAsync(); executor.execute(new Runnable() { @Override public void run() { try { System.out.println("副线程开始。。。"+Thread.currentThread()+"==>"+System.currentTimeMillis()); sayHello(); //获取到异步上下文 AsyncContext asyncContext = request.getAsyncContext(); //4、获取响应 ServletResponse response = asyncContext.getResponse(); response.getWriter().write("hello async..."); System.out.println("副线程结束。。。"+Thread.currentThread()+"==>"+System.currentTimeMillis()); startAsync.complete(); } catch (Exception e) { } } }); } public void sayHello() throws Exception{ System.out.println(Thread.currentThread()+" processing..."); Thread.sleep(3000); } }
测试结果如下:
副线程开始。。。Thread[pool-1-thread-1,5,main]==>1524210139124 Thread[pool-1-thread-1,5,main] processing... 副线程结束。。。Thread[pool-1-thread-1,5,main]==>1524210142125
【3】NonBlockingAsyncHelloServlet
Servlet 3.0对请求的处理虽然是异步的,但是对InputStream
和OutputStream
的IO操作却依然是阻塞的,对于数据量大的请求体或者返回体,阻塞IO也将导致不必要的等待。
因此在Servlet 3.1中引入了非阻塞IO(参考下图红框内容),通过在HttpServletRequest和HttpServletResponse中分别添加ReadListener和WriterListener方式,只有在IO数据满足一定条件时(比如数据准备好时),才进行后续的操作。
实例代码如下:
import javax.servlet.AsyncContext; import javax.servlet.ReadListener; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.ServletResponse; 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.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @WebServlet(value = "/nonBlockingThreadPoolAsync", asyncSupported = true) public class NonBlockingAsyncHelloServlet extends HttpServlet { private static ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 200, 50000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100)); protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { AsyncContext startAsync = request.startAsync(); ServletInputStream inputStream = request.getInputStream(); inputStream.setReadListener(new ReadListener() { @Override public void onDataAvailable() throws IOException { } @Override public void onAllDataRead() throws IOException { executor.execute(new Runnable() { @Override public void run() { try { System.out.println("副线程开始。。。"+Thread.currentThread()+"==>"+System.currentTimeMillis()); sayHello(); //获取到异步上下文 AsyncContext asyncContext = request.getAsyncContext(); //4、获取响应 ServletResponse response = asyncContext.getResponse(); response.getWriter().write("hello async..."); System.out.println("副线程结束。。。"+Thread.currentThread()+"==>"+System.currentTimeMillis()); startAsync.complete(); } catch (Exception e) { } } }); } @Override public void onError(Throwable t) { startAsync.complete(); } }); } public void sayHello() throws Exception{ System.out.println(Thread.currentThread()+" processing..."); Thread.sleep(3000); } }