正文
之前的
几篇文章
讲了 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连接的线程池是守护线程
”。