你提出了两个问题:
问题1:在Servlet的destroy()方法中调用System.exit()来强制终止任何非守护线程是一个好主意吗?
在任何与servlet相关的方法中调用System.exit()都是100%不正确的。你的代码并不是在JVM中唯一运行的代码,即使你是唯一的servlet运行(servlet容器有资源需要在JVM真正退出时进行清理)。
处理这种情况的正确方法是在destroy()方法中清理你的线程。这意味着以一种让你能够正确地温和地停止它们的方式启动它们。以下是一个示例(其中MyThread是你的一个线程,并且扩展了ServletManagedThread):
public class MyServlet extends HttpServlet {
private List<ServletManagedThread> threads = new ArrayList<ServletManagedThread>();
public void init() {
ServletManagedThread t = new MyThread();
threads.add(t);
t.start();
}
public void destroy() {
for(ServletManagedThread thread : threads) {
thread.stopExecuting();
}
}
}
public abstract class ServletManagedThread extends Thread {
private boolean keepGoing = true;
protected abstract void doSomeStuff();
protected abstract void probablySleepForABit();
protected abstract void cleanup();
public void stopExecuting() {
keepRunning = false;
}
public void run() {
while(keepGoing) {
doSomeStuff();
probablySleepForABit();
}
this.cleanup();
}
}
值得注意的是,有一些线程/并发库可以帮助处理这个问题 - 但如果您确实在servlet初始化时启动了一些线程,并且应该在servlet被销毁之前一直运行,那么这可能就是你需要的全部。
问题2: 为什么Tomcat 5.0.30及其后续版本(包括Tomcat 6.x.x)在Servlet的destroy()方法中存在System.exit()时无法正确关闭?
没有更多的分析,很难确定。
Microsoft说当Windows要求服务关闭但请求超时时,会出现错误1053。这似乎表明Tomcat内部发生了某些严重的问题。我肯定会怀疑您对
System.exit()
的调用可能是罪魁祸首。Tomcat(特别是Catalina)确实与VM注册了一个shutdown hook(至少在5.0.30中,参见
org.apache.catalina.startup.Catalina.start()
)。当您调用
System.exit()
时,该关机钩将由JVM调用。关机钩委托给正在运行的服务,因此每个服务都可能需要做很多工作。
如果关机钩(
由您的System.exit()
触发)无法执行(例如死锁等),则非常容易理解为何会出现错误1053,根据
Runtime.exit(int)
方法的文档(从
System.exit()
调用):
如果在虚拟机开始其关闭序列之后调用此方法,则如果正在运行关闭挂钩,则此方法将无限期地阻塞。如果已经运行了关闭挂钩并启用了退出终止,则如果状态为非零,则此方法使用给定的状态代码停止虚拟机;否则,它会无限期地阻塞。
这种“无限期阻塞”的行为肯定会导致错误1053。
如果您想要比这更完整的答案,可以
下载源代码并自行调试。
但是,我愿意打赌,如果您正确处理线程管理问题(如上所述),您的问题将消失。
简而言之,将System.exit()调用留给Tomcat - 那不是您的工作。