在Servlet 3.0之前,Servlet采用Thread-Per-Request的方式处理请求,即每一次Http请求都由某一个线程从头到尾负责处理。如果一个请求需要进行IO操作,比如访问数据库、调用第三方服务接口等,那么其所对应的线程将同步地等待IO操作完成, 而IO操作是非常慢的,所以此时的线程并不能及时地释放回线程池以供后续使用,在并发量越来越大的情况下,这将带来严重的性能问题。即便是像Spring、Struts这样的高层框架也脱离不了这样的桎梏,因为他们都是建立在Servlet之上的。为了解决这样的问题,Servlet 3.0引入了异步处理,然后在Servlet 3.1中又引入了非阻塞IO来进一步增强异步处理的性能。
在Servlet 3.0中,我们可以从HttpServletRequest对象中获得一个AsyncContext对象,该对象构成了异步处理的上下文,Request和Response对象都可从中获取。AsyncContext可以从当前线程传给另外的线程,并在新的线程中完成对请求的处理并返回结果给客户端,初始线程便可以还回给容器线程池以处理更多的请求。如此,通过将请求从一个线程传给另一个线程处理的过程便构成了Servlet 3.0中的异步处理。
举个例子,对于一个需要完成长时处理的Servlet来说,其实现通常为:
package davenkin.servlet;
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;
@WebServlet("/syncHello")
public class SyncHelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
new LongRunningProcess().run();
response.getWriter().write("Hello World!");
}
}
为了模拟长时处理过程,我们创建了一个LongRunningProcess类,其run()方法将随机地等待2秒之内的一个时间:
package davenkin.servlet;
import java.util.concurrent.ThreadLocalRandom;
/**
* Created by yteng on 3/14/17.
*/
public class LongRunningProcess {
public void run() {
try {
int millis = ThreadLocalRandom.current().nextInt(2000);
String currentThread = Thread.currentThread().getName();
System.out.println(currentThread + " sleep for " + millis + " milliseconds.");
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
此时的SyncHelloServlet将顺序地先执行LongRunningProcess的run()方法,然后将将Hello