Tomcat 7 的一次請求分析(一)處理線程的產生

在默認的配置下Tomcat啓動好以後會看到後臺上總共有6個線程在運行。其中1個用戶線程,剩下5個爲守護線程(以下圖所示)。 web

若是你對用戶線程、守護線程等概念不熟悉,請參看前一篇文章—— 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 節點時是這麼處理的:apache

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 方法的調用:tomcat

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 節點共有兩處配置:bash

<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 方法:app

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對象:async

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的父類。 ide

接下來容器啓動各組件時會調用 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 方法:post

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();
    }
複製代碼

以上即是本文開頭所述的兩個後臺線程產生和啓動的流程,其相關類調用的時序圖以下圖所示:

同理,ajp-bio-8009-Acceptor-0 和 ajp-bio-8009-AsyncTimeout 兩個守護線程的產生和啓動方式也是一致的,再也不贅述。
相關文章
相關標籤/搜索