正确理解servlet3.0规范之异步特性

如果想了解有关servlet3.0特性,推荐下面这篇ibm的文章:

https://www.ibm.com/developerworks/cn/java/j-lo-servlet30/index.html#icomments

servlet3.0增加了对异步的支持,在servlet3.0之前,客户端请求到达servlet后,servlet通常会执行一些比较耗时的外部操作,比如数据库操作、I/O操作、跨网络调用等,往往会阻塞当前servlet线程,当前的线程是由servlet容器(tomcat)管理并分配的,容器线程池为请求分配的线程会持有一系列的servlet资源,因为该线程会调用一系列方法(大部分为tomcat内部方法),而方法内部可能会持有很多线程私有的对象,比如在一个有过滤器的web应用中,每个线程将持有私有的filterChain对象。如果阻塞时间过长,那么在这段时间内此线程无法被回收,为资源分配的内存一直被占用,无法被GC回收或者返回Object pool,且该线程也无法分配给其他客户端请求,在一定程度上会造成并发量的降低。

因此,在servlet3.0中,在servlet中执行完一些必要的操作后可以调用response.getWriter().flush()首先返回客户端一部分数据,因为flush()方法会将缓冲区的数据刷新到输出流中,但是注意,不能调用response.getWriter().close()方法(因为调用完close()方法后,此response的输出流就关闭了),并且response的contentType必须要被显式设置(否则浏览器在不知道接收到的数据类型的情况下不会部分显示数据,而选择等待所有数据都到来)可以将耗时较大的操作放到另外一个线程中执行,并把一个AsyncContext上下文传递过去,而当前线程直接返回以便servlet容器线程池能够将其回收。而另一个线程在执行完成后,再调用response.getWriter().flush()、response.getWriter().close()操作,将结果返回给客户端。

servlet3.0规范新增的异步处理支持,很多人都会理解错误,比如,这篇文章以及评论,错误地认为servlet3.0的异步就是servlet能够将部分结果返回给客户端,然后一段时间后再将剩余结果返回给客户端。就像这个问题中的提问者一样。

其实这种返回部分结果的特性,servlet2就可以实现,就是多次调用输出流的flush()方法即可,把缓冲区的数据刷新到输出流中,输出流就会把数据返回给浏览器。如果此时response的contentType被显式设置了,浏览器就会部分显示数据(chrome浏览器)。也就是说实现方式为多次调用flush()方法,并且只最后调用一次close()方法。

将部分结果返回给客户端并不算是异步,而异步真正的关注点在于后台处理的方式。返回部分结果,后台可以是同步的(提前flush,提高客户端体验),也可以是异步的。而servlet3.0规范中的异步,目的不是关注客户端体验,因为返回部分结果同样可以由同步实现:

writer.print("首先返回这句话,使客户端得到及时的响应");
writer.flush();//刷新到输出流
result = db.query();//进行数据库查询
writer.print(result);//返回剩余结果

真正的目的是使后台操作异步,提早回收由容器管理的servlet线程。

注:一直在想,在servlet2中另起一个线程,将response传递过去,并且在servlet中response不close,这不也能实现后台异步处理嘛? 为了印证这个想法,我就试了一下,结果返回的结果不稳定,很可能会丢失数据,多次刷新浏览器快速访问此servlet还会报出Exception in thread "Thread-13" org.apache.tomcat.jni.Error: 20005: An invalid socket was returned at org.apache.tomcat.jni.Socket.sendbb(Native Method) 的异常,原因应该是servlet线程执行完方法后,tomcat会自动调用response.close()方法,导致在另外一个线程中使用输出流时,客户端和服务器的连接已经关掉了。

测试代码如下:

@WebServlet(name = "noAsyncServlet",urlPatterns = "/noasync",asyncSupported = false)
public class NoAsyncServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        final PrintWriter writer = resp.getWriter();
        writer.write("first");
        writer.write("second\n");
        writer.flush();

        new Thread(new Task(writer)).start();
        writer.write("already give to another\n");
        writer.flush();
    }

    private class Task implements Runnable{
        PrintWriter writer = null;

        public Task(PrintWriter writer) {
            this.writer = writer;
        }

        public void run() {
            writer.write("begin handle\n");
            writer.flush();
            try {
                Thread.sleep(1000);
            } catch (Exception e) {

            }
            writer.write("end handle\n");
            writer.flush();
            writer.close();

        }
    }
}

你可能感兴趣的:(java)