在Servlet 3.0中,我们可以这么写来达到异步处理:
    此时,我们先通过request.startAsync()获取到该请求对应的AsyncContext,然后调用AsyncContext的start()方法进行异步处理,处理完毕后需要调用complete()方法告知Servlet容器。start()方法会向Servlet容器另外申请一个新的线程(可以是从Servlet容器中已有的主线程池获取,也可以另外维护一个线程池,不同容器实现可能不一样),然后在这个新的线程中继续处理请求,而原先的线程将被回收到主线程池中。事实上,这种方式对性能的改进不大,因为如果新的线程和初始线程共享同一个线程池的话,相当于闲置下了一个线程,但同时又占用了另一个线程。

    1. package davenkin.servlet;
    2. import javax.servlet.AsyncContext;
    3. import javax.servlet.ServletException;
    4. import javax.servlet.annotation.WebServlet;
    5. import javax.servlet.http.HttpServlet;
    6. import javax.servlet.http.HttpServletRequest;
    7. import javax.servlet.http.HttpServletResponse;
    8. import java.io.IOException;
    9. @WebServlet(value = "/simpleAsync", asyncSupported = true)
    10. public class SimpleAsyncHelloServlet extends HttpServlet {
    11. protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    12. AsyncContext asyncContext = request.startAsync();
    13. asyncContext.start(() -> {
    14. new LongRunningProcess().run();
    15. try {
    16. asyncContext.getResponse().getWriter().write("Hello World!");
    17. } catch (IOException e) {
    18. e.printStackTrace();
    19. }
    20. asyncContext.complete();
    21. });
    22. }
    23. }

    当然,除了调用AsyncContext的start()方法,我们还可以通过手动创建线程的方式来实现异步处理:

    1. package davenkin.servlet;
    2. import javax.servlet.AsyncContext;
    3. import javax.servlet.ServletException;
    4. import javax.servlet.annotation.WebServlet;
    5. import javax.servlet.http.HttpServlet;
    6. import javax.servlet.http.HttpServletRequest;
    7. import javax.servlet.http.HttpServletResponse;
    8. import java.io.IOException;
    9. @WebServlet(value = "/newThreadAsync", asyncSupported = true)
    10. public class NewThreadAsyncHelloServlet extends HttpServlet {
    11. protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    12. AsyncContext asyncContext = request.startAsync();
    13. Runnable runnable = () -> {
    14. new LongRunningProcess().run();
    15. try {
    16. asyncContext.getResponse().getWriter().write("Hello World!");
    17. } catch (IOException e) {
    18. e.printStackTrace();
    19. }
    20. asyncContext.complete();
    21. };
    22. new Thread(runnable).start();
    23. }
    24. }

    自己手动创建新线程一般是不被鼓励的,并且此时线程不能重用。因此,一种更好的办法是我们自己维护一个线程池。这个线程池不同于Servlet容器的主线程池,如下图:
    image.png

    1. package davenkin.servlet;
    2. import javax.servlet.AsyncContext;
    3. import javax.servlet.ServletException;
    4. import javax.servlet.annotation.WebServlet;
    5. import javax.servlet.http.HttpServlet;
    6. import javax.servlet.http.HttpServletRequest;
    7. import javax.servlet.http.HttpServletResponse;
    8. import java.io.IOException;
    9. import java.util.concurrent.ArrayBlockingQueue;
    10. import java.util.concurrent.ThreadPoolExecutor;
    11. import java.util.concurrent.TimeUnit;
    12. @WebServlet(value = "/threadPoolAsync", asyncSupported = true)
    13. public class ThreadPoolAsyncHelloServlet extends HttpServlet {
    14. private static ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 200, 50000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100));
    15. protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    16. AsyncContext asyncContext = request.startAsync();
    17. executor.execute(() -> {
    18. new LongRunningProcess().run();
    19. try {
    20. asyncContext.getResponse().getWriter().write("Hello World!");
    21. } catch (IOException e) {
    22. e.printStackTrace();
    23. }
    24. asyncContext.complete();
    25. });
    26. }
    27. }

    Servlet 3.0对请求的处理虽然是异步的,但是对InputStream和OutputStream的IO操作却依然是阻塞的,对于数据量大的请求体或者返回体,阻塞IO也将导致不必要的等待。因此在Servlet 3.1中引入了非阻塞IO(参考下图红框内容),通过在HttpServletRequest和HttpServletResponse中分别添加ReadListener和WriterListener方式,只有在IO数据满足一定条件时(比如数据准备好时),才进行后续的操作。
    image.png
    我们为ServletInputStream添加了一个ReadListener,并在ReadListener的onAllDataRead()方法中完成了长时处理过程

    1. package davenkin.servlet;
    2. import javax.servlet.AsyncContext;
    3. import javax.servlet.ReadListener;
    4. import javax.servlet.ServletException;
    5. import javax.servlet.ServletInputStream;
    6. import javax.servlet.annotation.WebServlet;
    7. import javax.servlet.http.HttpServlet;
    8. import javax.servlet.http.HttpServletRequest;
    9. import javax.servlet.http.HttpServletResponse;
    10. import java.io.IOException;
    11. import java.util.concurrent.ArrayBlockingQueue;
    12. import java.util.concurrent.ThreadPoolExecutor;
    13. import java.util.concurrent.TimeUnit;
    14. @WebServlet(value = "/nonBlockingThreadPoolAsync", asyncSupported = true)
    15. public class NonBlockingAsyncHelloServlet extends HttpServlet {
    16. private static ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 200, 50000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100));
    17. protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    18. AsyncContext asyncContext = request.startAsync();
    19. ServletInputStream inputStream = request.getInputStream();
    20. inputStream.setReadListener(new ReadListener() {
    21. @Override
    22. public void onDataAvailable() throws IOException {
    23. }
    24. @Override
    25. public void onAllDataRead() throws IOException {
    26. executor.execute(() -> {
    27. new LongRunningProcess().run();
    28. try {
    29. asyncContext.getResponse().getWriter().write("Hello World!");
    30. } catch (IOException e) {
    31. e.printStackTrace();
    32. }
    33. asyncContext.complete();
    34. });
    35. }
    36. @Override
    37. public void onError(Throwable t) {
    38. asyncContext.complete();
    39. }
    40. });
    41. }
    42. }