Tomcat主线程监听SHUTDOWN,如何远程关闭Tomcat?守护线程守护了谁?

您所在的位置:网站首页 shutdown端口 Tomcat主线程监听SHUTDOWN,如何远程关闭Tomcat?守护线程守护了谁?

Tomcat主线程监听SHUTDOWN,如何远程关闭Tomcat?守护线程守护了谁?

#Tomcat主线程监听SHUTDOWN,如何远程关闭Tomcat?守护线程守护了谁?| 来源: 网络整理| 查看: 265

首发CSDN:徐同学呀,原创不易,转载请注明源链接。我是徐同学,用心输出高质量文章,希望对你有所帮助。本篇文章有些简单,周末水了水了。。。

文章目录 一、前言守护线程守护了谁 二、Tomcat主线程监听SHUTDOWN源码展示 三、模拟Tomcat远程关闭1、正常远程关闭2、非守护用户线程下远程关闭 四、经验总结

一、前言

本来不想写这篇文章的,因为Tomcat监听SHUTDOWN的原理实在太简单,前面几篇Tomcat系列文章也提到过。但是,昨天上班的时候写了一个定时任务,放在公司的分布式任务调度平台上,定了每2分钟运行一次,开开心心点击启动,然后就去做别的事了。

过了一段时间,打开调度平台,查看调度情况,我惊呆了,一长溜显示调度成功,但还在运行的调度。我寻思难道任务执行器回调出问题了?心跳检测了一下挺正常;难道是我的定时任务很耗时,超过了2分钟,都排队运行?不应该呀,就扫描更新个数据,而且本地测试数据很少的。于是制造了一些假数据,复制任务命令到机器上运行,日志打印显示成功,耗时4秒多,但迟迟不退出。

经过郁闷+打堆栈,发现有一个线程循环运行导致主线程不能正常退出,后来查到是业务代码里调用了同事写的一个组件,而这个组件里会起一个定时线程,还是个非守护线程,才导致我的任务主线程运行完不能正常退出。

原因找到了,解决方法也很简单,就是在任务代码里也新建一个Thread,并设置成守护线程,在这个守护线程里调用同事写的组件,用Future同步获取结果。这样组件里的定时线程也是守护线程了,主线程就可以正常退出了。

守护线程守护了谁

守护线程的概念我知道呀,线程分为两种,一种是非守护线程,一种是守护线程,当所有的非守护线程退出后,守护线程因为没有了守护对象也就跟着结束了。需要注意:

创建线程时如果没有明确setDaemon(true),一般默认是非守护线程。守护线程里创建的子线程默认也是守护线程,除非setDaemon(false)。 二、Tomcat主线程监听SHUTDOWN

Tomcat中的线程也有两种,主线程是非守护线程,其他单独创建的线程或者线程池创建的工作线程都默认是守护线程。

当Tomcat启动时,做完所有初始化和启动工作,主线程会进入一个无限循环监听默认8005端口的状态,直到网络中读取到SHUTDOWN指令,才会退出循环,进而调用Tomcat停止销毁操作。

源码展示

org.apache.catalina.startup.Catalina#start

public void start() { // 省略调用Server init、start等操作 if (await) { // 调用Server的await,循环等待shutdown指令 await(); // 如果接收到shutdown,就结束await(),调用stop停止Tomcat stop(); } }

源码很简单,建立一个ServerSocket,循环监听读取网络中是否有SHUTDOWN指令传来:

public void await() { // 省略部分无关紧要代码 // Set up a server socket to wait on try { // 建立一个server socket 端口默认为8005 awaitSocket = new ServerSocket(getPortWithOffset(), 1, InetAddress.getByName(address)); } catch (IOException e) { log.error(sm.getString("standardServer.awaitSocket.fail", address, String.valueOf(getPortWithOffset()), String.valueOf(getPort()), String.valueOf(getPortOffset())), e); return; } try { awaitThread = Thread.currentThread(); // Loop waiting for a connection and a valid command while (!stopAwait) { ServerSocket serverSocket = awaitSocket; if (serverSocket == null) { break; } // Wait for the next connection Socket socket = null; StringBuilder command = new StringBuilder(); try { InputStream stream; long acceptStartTime = System.currentTimeMillis(); try { socket = serverSocket.accept(); socket.setSoTimeout(10 * 1000); // Ten seconds stream = socket.getInputStream(); } catch (SocketTimeoutException ste) { // This should never happen but bug 56684 suggests that // it does. log.warn(sm.getString("standardServer.accept.timeout", Long.valueOf(System.currentTimeMillis() - acceptStartTime)), ste); continue; } catch (AccessControlException ace) { log.warn(sm.getString("standardServer.accept.security"), ace); continue; } catch (IOException e) { if (stopAwait) { // Wait was aborted with socket.close() break; } log.error(sm.getString("standardServer.accept.error"), e); break; } // 读取 command while (expected > 0) { int ch = -1; try { ch = stream.read(); } catch (IOException e) { log.warn(sm.getString("standardServer.accept.readError"), e); ch = -1; } // Control character or EOF (-1) terminates loop if (ch // Close the socket now that we are done with it try { if (socket != null) { socket.close(); } } catch (IOException e) { // Ignore } } // Match against our command string // 匹配shutdown指令 boolean match = command.toString().equals(shutdown); if (match) { log.info(sm.getString("standardServer.shutdownViaPort")); // 匹配成功,退出循环 break; } else log.warn(sm.getString("standardServer.invalidShutdownCommand", command.toString())); } } finally { ServerSocket serverSocket = awaitSocket; awaitThread = null; awaitSocket = null; // Close the server socket and return if (serverSocket != null) { try { serverSocket.close(); } catch (IOException e) { // Ignore } } } }

Tomcat的Connector是在线程池里处理请求连接,可以引用公共的Executor组件,也可以创建私有的线程池,线程池中创建的工作线程默认都是守护线程,这样web项目里创建的线程,默认也都是守护线程。Tomcat主线程退出,web项目中的用户线程也跟着退出。

如果用户线程被私自设置成非守护线程,或者设置Connector中的线程池创建的工作线程是非守护的,就会导致用户的非守护线程阻碍Tomcat主线程的正常退出。

三、模拟Tomcat远程关闭 1、正常远程关闭

运行Tomcat,并保持默认SHUTDOWN端口8005。Tomcat启动成功后,运行如下代码即可向8005发送SHUTDOWN指令:

public class TestTomcatShutdown { public static void main(String[] args) throws InterruptedException { Socket socket = null; try { socket = new Socket("127.0.0.1", 8005); String shutdown = "SHUTDOWN"; socket.getOutputStream().write(shutdown.getBytes()); } catch (IOException e) { e.printStackTrace(); } finally { if (socket != null) { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } } }

从Tomcat日志中可以看出,Tomcat收到了一个合法的SHUTDOWN指令,进而调用了一些停止销毁操作。

await A valid shutdown command

2、非守护用户线程下远程关闭

如果用户创建的线程是非守护线程,看看Tomcat收到SHUTDOWN指令后能否正常退出。简单写一个Servlet并创建一个循环运行的非守护线程,部署到Tomcat中:

package com.stefan; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; public class TestShutdownServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("TestShutdownServlet doGet...."); System.out.println(TestShutdownServlet.class.getClassLoader()); System.out.println(Thread.currentThread().getName()); TestThread testThread = new TestThread(); testThread.setDaemon(false); testThread.start(); System.out.println("testThread.isDaemon()=" + testThread.isDaemon()); } } package com.stefan; public class TestThread extends Thread { @Override public void run() { while (true) { System.out.println("hhhhhhhh"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } } }

将上述两个类编译,并建立一个web目录test,放到webapps下即可:

test放在webapps下

在web.xml里指定servlet映射:

test tomcat SHUTDOWN test tomcat SHUTDOWN UTF-8 TestShutdownServlet com.stefan.TestShutdownServlet TestShutdownServlet /testShutdown

启动Tomcat会自动部署test项目,访问http://127.0.0.1:8081/test/testShutdown

非守护用户线程下远程关闭Tomcat

此时再运行远程关闭代码:

Tomcat没有正常退出

Tomcat接收到了SHUTDOWN指令,并做了停止销毁操作,但依然没有退出,也无法对外提供服务。

四、经验总结

这种远程关闭Tomcat的方式,在实际生产中可能并不常见,至少我现在的公司web项目没有这样做。更常见的是ps查看Tomcat进程id,然后直接kill,没必要还写个socket客户端,发送SHUTDOWN给Tomcat,但也是多了一种选择。其实ps+kill的方式也不简单,这是两个操作,如果需要实现远程关闭,还要需要免密的ssh远程传输命令。

此次文章内容较为简单,但是至少对守护线程有了一个更深的认识,不止停留于概念。在一些开源组件中看到创建线程也一般都是会设置为守护线程,我觉得这是一种规范吧。

Tomcat源码详细注释链接(非推广,持续更新):https://gitee.com/stefanpy/tomcat-source-code-learning

如若文章有错误理解,欢迎批评指正,同时非常期待你的评论、点赞和收藏。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3