专栏名称: 预流
敲代码的
目录
相关文章推荐
新浪科技  ·  #蔚来2024年交付量创历史新高# ... ·  昨天  
新浪科技  ·  【#特斯拉上海工厂首批Megapack出口# ... ·  昨天  
新浪科技  ·  【#传商汤科技联合创始人徐冰将辞职# ... ·  2 天前  
51好读  ›  专栏  ›  预流

Tomcat 7 服务器关闭原理

预流  · 掘金  ·  · 2018-02-01 02:19

正文

之前的 几篇文章 讲了 Tomcat 的启动过程,在默认的配置下启动完之后会看到后台实际上总共有 6 个线程在运行。即 1 个用户线程,剩下 5 个为守护线程(下图中的 Daemon Thread )。

如果对什么叫守护线程的概念比较陌生,这里再重复一下:

所谓守护线程,是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程。这种线程并不属于程序中不可或缺的部分,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。

用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。将线程转换为守护线程可以通过调用 Thread 对象的 setDaemon(true) 方法来实现。

Tomcat 的关闭正是利用了这个原理,即只要将那唯一的一个用户线程关闭,则整个应用就关闭了。

要研究这个用户线程怎么被关闭的得先从这个线程从何产生说起。在前面分析 Tomcat 的启动时我们是从 org.apache.catalina.startup.Bootstrap 类的 main 方法作为入口,该类的 453 到 456 行是 Tomcat 启动时会执行的代码:

前面的文章里分析了 daemon.load 和 daemon.start 方法,这里请注意 daemon.setAwait(true); 这句,它的作用是通过反射调用 org.apache.catalina.startup.Catalina 类的 setAwait(true) 方法,最终将 Catalina 类的实例变量 await 设值为 true 。

Catalina 类的 setAwait 方法代码:

    /**
     * Set flag.
     */
    public void setAwait(boolean await)
        throws Exception {

        Class<?> paramTypes[] = new Class[1];
        paramTypes[0] = Boolean.TYPE;
        Object paramValues[] = new Object[1];
        paramValues[0] = Boolean.valueOf(await);
        Method method =
            catalinaDaemon.getClass().getMethod("setAwait", paramTypes);
        method.invoke(catalinaDaemon, paramValues);

    }

如前文分析,Tomcat 启动时会调用 org.apache.catalina.startup.Catalina 类的 start 方法,看下这个方法的代码:

     
     1	    /**
     2	     * Start a new server instance.
     3	     */
     4	    public void start() {
     5	
     6	        if (getServer() == null) {
     7	            load();
     8	        }
     9	
    10	        if (getServer() == null) {
    11	            log.fatal("Cannot start server. Server instance is not configured.");
    12	            return;
    13	        }
    14	
    15	        long t1 = System.nanoTime();
    16	
    17	        // Start the new server
    18	        try {
    19	            getServer().start();
    20	        } catch (LifecycleException e) {
    21	            log.fatal(sm.getString("catalina.serverStartFail"), e);
    22	            try {
    23	                getServer().destroy();
    24	            } catch (LifecycleException e1) {
    25	                log.debug("destroy() failed for failed Server ", e1);
    26	            }
    27	            return;
    28	        }
    29	
    30	        long t2 = System.nanoTime();
    31	        if(log.isInfoEnabled()) {
    32	            log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
    33	        }
    34	
    35	        // Register shutdown hook
    36	        if (useShutdownHook) {
    37	            if (shutdownHook == null) {
    38	                shutdownHook = new CatalinaShutdownHook();
    39	            }
    40	            Runtime.getRuntime().addShutdownHook(shutdownHook);
    41	
    42	            // If JULI is being used, disable JULI's shutdown hook since
    43	            // shutdown hooks run in parallel and log messages may be lost
    44	            // if JULI's hook completes before the CatalinaShutdownHook()
    45	            LogManager logManager = LogManager.getLogManager();
    46	            if (logManager instanceof ClassLoaderLogManager) {
    47	                ((ClassLoaderLogManager) logManager).setUseShutdownHook(
    48	                        false);
    49	            }
    50	        }
    51	
    52	        if (await) {
    53	            await();
    54	            stop();
    55	        }
    56	    }

前文分析启动时发现通过第 19 行 getServer().start() 的这次方法调用,Tomcat 接下来会一步步启动所有在配置文件中配置的组件。后面的代码没有分析,这里请关注最后第 52 到 55 行,上面说到已经将 Catalina 类的实例变量 await 设值为 true,所以这里将会执行 Catalina 类的 await 方法:

    /**
     * Await and shutdown.
     */
    public void await() {
    
        getServer().await();
        
    }

该方法就一句话,意思是调用 org.apache.catalina.core.StandardServer 类的 await 方法:


     1	    /**
     2	     * Wait until a proper shutdown command is received, then return.
     3	     * This keeps the main thread alive - the thread pool listening for http 
     4	     * connections is daemon threads.
     5	     */
     6	    @Override
     7	    public void await() {
     8	        // Negative values - don't wait on port - tomcat is embedded or we just don't like ports
     9	        if( port == -2 ) {
    10	            // undocumented yet - for embedding apps that are around, alive.
    11	            return;
    12	        }
    13	        if( port==-1 ) {
    14	            try {
    15	                awaitThread = Thread.currentThread();
    16	                while(!stopAwait) {
    17	                    try {
    18	                        Thread.sleep( 10000 );
    19	                    } catch( InterruptedException ex ) {
    20	                        // continue and check the flag
    21	                    }
    22	                }
    23	            } finally {
    24	                awaitThread = null;
    25	            }
    26	            return;
    27	        }
    28	
    29	        // Set up a server socket to wait on
    30	        try {
    31	            awaitSocket = new ServerSocket(port, 1,
    32	                    InetAddress.getByName(address));
    33	        } catch (IOException e) {
    34	            log.error("StandardServer.await: create[" + address
    35	                               + ":" + port
    36	                               + "]: ", e);
    37	            return;
    38	        }
    39	
    40	        try {
    41	            awaitThread = Thread.currentThread();
    42	
    43	            // Loop waiting for a connection and a valid command
    44	            while (!stopAwait) {
    45	                ServerSocket serverSocket = awaitSocket;
    46	                if (serverSocket == null) {
    47	                    break;
    48	                }
    49	    
    50	                // Wait for the next connection
    51	                Socket socket = null;
    52	                StringBuilder command = new StringBuilder();
    53	                try {
    54	                    InputStream stream;
    55	                    try {
    56	                        socket = serverSocket.accept();
    57	                        socket.setSoTimeout(10 * 1000);  // Ten seconds
    58	                        stream = socket.getInputStream();
    59	                    } catch (AccessControlException ace) {
    60	                        log.warn("StandardServer.accept security exception: "
    61	                                + ace.getMessage(), ace);
    62	                        continue;
    63	                    } catch (IOException e) {
    64	                        if (stopAwait) {
    65	                            // Wait was aborted with socket.close()
    66	                            break;
    67	                        }
    68	                        log.error("StandardServer.await: accept: ", e);
    69	                        break;
    70	                    }
    71	
    72	                    // Read a set of characters from the socket
    73	                    int expected = 1024; // Cut off to avoid DoS attack
    74	                    while (expected < shutdown.length()) {
    75	                        if (random == null)
    76	                            random = new Random();
    77	                        expected += (random.nextInt() % 1024);
    78	                    }
    79	                    while (expected > 0) {
    80	                        int ch = -1;
    81	                        try {
    82	                            ch = stream.read();
    83	                        } catch (IOException e) {
    84	                            log.warn("StandardServer.await: read: ", e);
    85	                            ch = -1;
    86	                        }
    87	                        if (ch < 32)  // Control character or EOF terminates loop
    88	                            break;
    89	                        command.append((char) ch);
    90	                        expected--;
    91	                    }
    92	                } finally {
    93	                    // Close the socket now that we are done with it
    94	                    try {
    95	                        if (socket != null) {
    96	                            socket.close();
    97	                        }
    98	                    } catch (IOException e) {
    99	                        // Ignore
   100	                    }
   101	                }
   102	
   103	                // Match against our command string
   104	                boolean match = command.toString().equals(shutdown);
   105	                if (match) {
   106	                    log.info(sm.getString("standardServer.shutdownViaPort"));
   107	                    break;
   108	                } else
   109	                    log.warn("StandardServer.await: Invalid command '"
   110	                            + command.toString() + "' received");
   111	            }
   112	        } finally {
   113	            ServerSocket serverSocket = awaitSocket;
   114	            awaitThread = null;
   115	            awaitSocket = null;
   116	
   117	            // Close the server socket and return
   118	            if (serverSocket != null) {
   119	                try {
   120	                    serverSocket.close();
   121	                } catch (IOException e) {
   122	                    // Ignore
   123	                }
   124	            }
   125	        }
   126	    }

这段代码就不一一分析,总体作用如方法前的注释所说,即“ 一直等待到接收到一个正确的关闭命令后该方法将会返回。这样会使主线程一直存活——监听http连接的线程池是守护线程 ”。







请到「今天看啥」查看全文