专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

Tomcat 7 的一次请求分析(一)处理线程的产生

在默认的配置下Tomcat启动好之后会看到后台上总共有6个线程在运行。其中1个用户线程,剩下5个为守护线程(如下图所示)。

63_1.png如果你对用户线程、守护线程等概念不熟悉,请参看前一篇文章—— Tomcat 7 服务器关闭原理。 这里重点关注以 http-bio-8080 开头的两个守护线程(即 http-bio-8080-Acceptor-0 和 http-bio-8080-AsyncTimeout ),因为这是我们在 Tomcat 的默认配置下 web 应用时实际处理请求的线程。先看下这两个线程在容器启动时是如何产生和启动的。

在前面将 Tomcat 启动的系列文章中看到 Tomcat 容器启动时会用 Digester 读取 server.xml 文件产生相应的组件对象并采取链式调用的方式调用它们的 init 和 start 方法,在 Digester 读取到 server.xml 中的 connector 节点时是这么处理的:

        digester.addRule("Server/Service/Connector",
                         new ConnectorCreateRule());
        digester.addRule("Server/Service/Connector",
                         new SetAllPropertiesRule(new String[]{"executor"}));
        digester.addSetNext("Server/Service/Connector",
                            "addConnector",
                            "org.apache.catalina.connector.Connector");

以上代码见org.apache.catalina.startup.Catalina类的 366 到 372 行。所以在碰到 server.xml 文件中的 Server/Service/Connector 节点时将会触发 ConnectorCreateRule 类的 begin 方法的调用:

     1      public void begin(String namespace, String name, Attributes attributes)
     2              throws Exception {
     3          Service svc = (Service)digester.peek();
     4          Executor ex = null;
     5          if ( attributes.getValue("executor")!=null ) {
     6              ex = svc.getExecutor(attributes.getValue("executor"));
     7          }
     8          Connector con = new Connector(attributes.getValue("protocol"));
     9          if ( ex != null )  _setExecutor(con,ex);
    10          
    11          digester.push(con);
    12      }

在第 8 行,会根据配置文件中 Server/Service/Connector 节点的 protocol 属性调用 org.apache.catalina.connector.Connector 类的构造方法,而默认情况下 server.xml 文件中 Server/Service/Connector 节点共有两处配置:

<Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />

<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

先看第一个 Connector 节点,调用 Connector 的构造方法时会传入字符串 HTTP/1.1

     1      public Connector(String protocol) {
     2          setProtocol(protocol);
     3          // Instantiate protocol handler
     4          try {
     5              Class<?> clazz = Class.forName(protocolHandlerClassName);
     6              this.protocolHandler = (ProtocolHandler) clazz.newInstance();
     7          } catch (Exception e) {
     8              log.error(sm.getString(
     9                      "coyoteConnector.protocolHandlerInstantiationFailed"), e);
    10          }
    11      }

这里先会执行 org.apache.catalina.connector.Connector 类的 setProtocol 方法:

     1      public void setProtocol(String protocol) {
     2  
     3          if (AprLifecycleListener.isAprAvailable()) {
     4              if ("HTTP/1.1".equals(protocol)) {
     5                  setProtocolHandlerClassName
     6                      ("org.apache.coyote.http11.Http11AprProtocol");
     7              } else if ("AJP/1.3".equals(protocol)) {
     8                  setProtocolHandlerClassName
     9                      ("org.apache.coyote.ajp.AjpAprProtocol");
    10              } else if (protocol != null) {
    11                  setProtocolHandlerClassName(protocol);
    12              } else {
    13                  setProtocolHandlerClassName
    14                      ("org.apache.coyote.http11.Http11AprProtocol");
    15              }
    16          } else {
    17              if ("HTTP/1.1".equals(protocol)) {
    18                  setProtocolHandlerClassName
    19                      ("org.apache.coyote.http11.Http11Protocol");
    20              } else if ("AJP/1.3".equals(protocol)) {
    21                  setProtocolHandlerClassName
    22                      ("org.apache.coyote.ajp.AjpProtocol");
    23              } else if (protocol != null) {
    24                  setProtocolHandlerClassName(protocol);
    25              }
    26          }
    27  
    28      }

所以此时会调用setProtocolHandlerClassName("org.apache.coyote.http11.Http11Protocol")从而将 Connector 类实例变量 protocolHandlerClassName 值设置为org.apache.coyote.http11.Http11Protocol,接下来在 Connector 的构造方法中就会根据 protocolHandlerClassName 变量的值产生一个org.apache.coyote.http11.Http11Protocol对象,并将该对象赋值给 Connector 类的实例变量 protocolHandler 。在 Http11Protocol 类的构造方法中会产生一个org.apache.tomcat.util.net.JIoEndpoint对象:


1 public Http11Protocol() { 2 endpoint = new JIoEndpoint(); 3 cHandler = new Http11ConnectionHandler(this); 4 ((JIoEndpoint) endpoint).setHandler(cHandler); 5 setSoLinger(Constants.DEFAULT_CONNECTION_LINGER); 6 setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT); 7 setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY); 8 }

几个相关对象的构造方法调用时序图如下所示,其中org.apache.coyote.AbstractProtocolorg.apache.coyote.http11.Http11Protocol的父类org.apache.tomcat.util.net.AbstractEndpointorg.apache.tomcat.util.net.JIoEndpoint的父类。

63_2.png接下来容器启动各组件时会调用 org.apache.catalina.connector.Connector的 start 方法,如前面分析 Tomcat 启动时所述,此时会调用 org.apache.catalina.connector.Connector类的 startInternal 方法:

    protected void startInternal() throws LifecycleException {

        // Validate settings before starting
        if (getPort() < 0) {
            throw new LifecycleException(sm.getString(
                    "coyoteConnector.invalidPort", Integer.valueOf(getPort())));
        }

        setState(LifecycleState.STARTING);

        try {
            protocolHandler.start();
        } catch (Exception e) {
            String errPrefix = "";
            if(this.service != null) {
                errPrefix += "service.getName(): \"" + this.service.getName() + "\"; ";
            }

            throw new LifecycleException
                (errPrefix + " " + sm.getString
                 ("coyoteConnector.protocolHandlerStartFailed"), e);
        }

        mapperListener.start();
    }

在第 12 行,将会调用实例变量 protocolHandler 的 start 方法。在上面分析 Connector 类的构造函数时发现 protocolHandler 变量的值就是org.apache.coyote.http11.Http11Protocol对象,所以此时将会调用该类的 start 方法。在 Http11Protocol 类中没有定义 start 方法,这里将会调用其父类org.apache.coyote.AbstractProtocol中的 start 方法:

    public void start() throws Exception {
        if (getLog().isInfoEnabled())
            getLog().info(sm.getString("abstractProtocolHandler.start",
                    getName()));
        try {
            endpoint.start();
        } catch (Exception ex) {
            getLog().error(sm.getString("abstractProtocolHandler.startError",
                    getName()), ex);
            throw ex;
        }
    }

这里会调用 endpoint 对象的 start 方法,而 endpoint 是org.apache.tomcat.util.net.JIoEndpoint类的实例(在上面讲 Http11Protocol 类的构造方法时所提到),这里最终会执行该类的 startInternal 方法:

     1      @Override
     2      public void startInternal() throws Exception {
     3  
     4          if (!running) {
     5              running = true;
     6              paused = false;
     7  
     8              // Create worker collection
     9              if (getExecutor() == null) {
    10                  createExecutor();
    11              }
    12  
    13              initializeConnectionLatch();
    14  
    15              startAcceptorThreads();
    16  
    17              // Start async timeout thread
    18              Thread timeoutThread = new Thread(new AsyncTimeout(),
    19                      getName() + "-AsyncTimeout");
    20              timeoutThread.setPriority(threadPriority);
    21              timeoutThread.setDaemon(true);
    22              timeoutThread.start();
    23          }
    24      }

正是在这里产生并启动本文开头提到的 http-bio-8080-Acceptor-0 和 http-bio-8080-AsyncTimeout 两个线程。第 17 到 22 行就是产生和启动 http-bio-8080-AsyncTimeout 线程,第 15 行这里调用父类org.apache.tomcat.util.net.AbstractEndpoint的 startAcceptorThreads 方法:

     1      protected final void startAcceptorThreads() {
     2          int count = getAcceptorThreadCount();
     3          acceptors = new Acceptor[count];
     4  
     5          for (int i = 0; i < count; i++) {
     6              acceptors[i] = createAcceptor();
     7              String threadName = getName() + "-Acceptor-" + i;
     8              acceptors[i].setThreadName(threadName);
     9              Thread t = new Thread(acceptors[i], threadName);
    10              t.setPriority(getAcceptorThreadPriority());
    11              t.setDaemon(getDaemon());
    12              t.start();
    13          }
    14      }
    15  
    16  
    17      /**
    18       * Hook to allow Endpoints to provide a specific Acceptor implementation.
    19       */
    20      protected abstract Acceptor createAcceptor();

在这里将产生和启动 http-bio-8080-Acceptor-0 线程。注意在构造该线程时第 6 行将会调用第 20 行的抽象方法,该方法的具体实现是在 JIoEndpoint 类中:

    @Override
    protected AbstractEndpoint.Acceptor createAcceptor() {
        return new Acceptor();
    }

以上便是本文开头所述的两个后台线程产生和启动的流程,其相关类调用的时序图如下图所示:

63_3.png同理,ajp-bio-8009-Acceptor-0 和 ajp-bio-8009-AsyncTimeout 两个守护线程的产生和启动方式也是一致的,不再赘述。

文章永久链接:https://tech.souyunku.com/35913

未经允许不得转载:搜云库技术团队 » Tomcat 7 的一次请求分析(一)处理线程的产生

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们