• Servlet特性研究之异步模式


    Servlet只有同步模型是怎样的?

    异步处理是Servlet3.0版本的重要功能之一,分析异步处理模型之前,先看看同步处理的过程是怎样的:

    • 客户端发起HTTP请求一个动态Servlet API,请求到达服务器端后经过静态服务器过滤后转交给Servlet容器,
    • 容器从主线程池获取一个线程,开始执行Servlet程序,执行结束后得到了完整的响应内容并将其返回给调用方
    • 然后将该线程还回线程池,整个过程都是由同一个主线程在执行。

    上述模型存在什么问题呢?

    • 作为服务器要想提高并发性能,就只能通过提高线程池的最大线程数。即吞吐量瓶颈受线程池约束。
    • 更严重的问题是:Servlet执行期间始终占用了该线程池线程,假如某个高频且耗时的Servlet被请求,那就会占用大量甚至全部的线程池线程,进而导致没有线程来处理其它请求。

    什么是异步处理模式?

    相对于前面的同步处理,异步处理的核心本质就是提供了一个突破点:

    让Servlet程序能够将这些慢操作分配给新线程来执行,同时尽快将该Servlet所占用的线程归还到容器线程池。

    先来看看异步模式的Servlet程序是怎么样的:

    import javax.servlet.AsyncContext;
    import javax.servlet.ServletException;
    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.io.PrintWriter;
    import java.util.Date;
    
    @WebServlet(urlPatterns = "/asyncapi", asyncSupported = true)
    public class AsyncServlet extends HttpServlet {
        public void service(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
            response.setContentType("text/html;charset=GBK");
            PrintWriter printWriter = response.getWriter();
            printWriter.println("异步Servlet示例");
            printWriter.println("进入Servlet的时间:" + new Date() + "
    "
    ); printWriter.println("执行Servlet的线程ID:" + Thread.currentThread().getId() + " Name=" + Thread.currentThread().getName() + "
    "
    ); // 创建AsyncContext,开始异步调用 final AsyncContext asyncContext = request.startAsync(); asyncContext.setTimeout(50 * 1000);// 设置异步调用的超时时长,限制HTTP响应的最大耗时 asyncContext.start( new Runnable() {//创建新线程继续执行 public void run() { try { Thread.sleep(1 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } printWriter.println("执行业务处理的线程ID:" + Thread.currentThread().getId() + " Name=" + Thread.currentThread().getName() + "
    "
    ); ServletResponse response = asyncContext.getResponse(); /* ... print to the response ... */ printWriter.println("请求处理结束:" + new Date() + "
    "
    ); asyncContext.complete(); } } ); printWriter.println("退出Servlet的时间:" + new Date() + "
    "
    ); } }

    请求该API,根据返回值可以看到现象:

    • Servlet内部模拟耗时了1秒,postman监测到实际耗时也是1秒
    • 处理该次HTTP请求期间,共使用了两个线程
    • 进入和离开Servlet的时间一致,说明该线程占用很短暂
    • 执行业务处理的耗时操作占用了另外一个线程,且执行完成后才返回HTTP响应

    至此,我们通过演示程序确实看到使用异步模式将Servet部分和耗时操作部分分配到了不同的线程执行。那么耗时操作用到的新线程来资源哪里?

    异步处理模式的实现原理

    根据文档描述,start()方法是启用新线程的关键,它是怎么实现的呢?扒拉源码看看吧

    这需要我们查看Servlet容器的具体实现来验证,此处以Tomcat 9.0源码为例进行分析。

    下载好源码后,首选找到接口AsyncContext及实现类AsyncContextImpl的源码:

    上图看到tomcat的状态机进行调度后续的处理。

    我们根据状态值检索到后续入口:

    最终看到是通过executor来执行runnable程序,而executor即tomcat中可配置的连接池。

    当然也不是只能使用start()来启用新线程,我们甚至可以手工new线程来执行耗时操作,演示如下:

    import javax.servlet.AsyncContext;
    import javax.servlet.ServletException;
    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.io.PrintWriter;
    import java.util.Date;
    
    @WebServlet(urlPatterns = "/asyncapi_newthread", asyncSupported = true)
    public class AsyncServlet_NewThread extends HttpServlet {
        public void service(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
            response.setContentType("text/html;charset=GBK");
            PrintWriter printWriter = response.getWriter();
            printWriter.println("异步Servlet示例");
            printWriter.println("进入Servlet的时间:" + new Date() + "
    "
    ); printWriter.println("执行Servlet的线程ID:" + Thread.currentThread().getId() + " Name=" + Thread.currentThread().getName() + "
    "
    ); // 创建AsyncContext,开始异步调用 final AsyncContext asyncContext = request.startAsync(); asyncContext.setTimeout(50 * 1000);// 设置异步调用的超时时长,限制HTTP响应的最大耗时 new Thread(() -> { try { Thread.sleep(1 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } printWriter.println("执行业务处理的线程ID:" + Thread.currentThread().getId() + " Name=" + Thread.currentThread().getName() + "
    "
    ); ServletResponse aResponse = asyncContext.getResponse(); /* ... print to the response ... */ printWriter.println("请求处理结束:" + new Date() + "
    "
    ); asyncContext.complete(); }).start(); printWriter.println("退出Servlet的时间:" + new Date() + "
    "
    ); } }

    异步模式带来的好处是什么?

    从编程方面提供了异步能力,在编程环节就可以提前将耗时较高的Servlet启用异步模式。

    异步模式具备拆分线程池解耦的能力,配置异步模式从专有work线程池取线程,而不是与原Servlet线程池共用线程,可以显著提高Servlet容器的并发量,减小对其它Serlvet请求的影响。

    同时要看到:异步模式只能说让Tomcat有机会接收更多请求,并不能提升特定服务的吞吐量。

    参考资料:

    Java EE7官方文档目录:https://docs.oracle.com/javaee/7/tutorial/index.html

    Servlet的异步处理:https://docs.oracle.com/javaee/7/tutorial/servlets012.htm

    Servlet的非阻塞IO:https://docs.oracle.com/javaee/7/tutorial/servlets013.htm

  • 相关阅读:
    一文看懂光模块的工作原理
    __str__和__repr__
    stream流-> 判定 + 过滤 + 收集
    【项目实战】自主实现 HTTP 项目(四)——处理请求和构建相应
    win10/Windows 10 纯净精简版美化
    web前端设计与开发期末作品 旅游咨询网站 HTML5期末大作业 HTML+CSS旅游社网站5个页面 关于制作网页主题论述
    JavaScript字符串常用方法
    通过实例理解 .gnu.hash section
    二十三种设计模式全面解析-单例设计模式:解密全局独一无二的实例创造者
    OptiCoupe 6:光学切割面板和型材切割优化[OptiCut]
  • 原文地址:https://www.cnblogs.com/chen943354/p/16598898.html