你问了两个问题:
问题 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>();
// lots of irrelevant stuff left out for brevity
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:如果Servlet的destroy()方法中有System.exit(),为什么Tomcat 5.0.30及(包括Tomcat 6.xx的后续版本)无法正常关闭?
没有更多的分析,很难确定。 Microsoft says 当 Windows 要求关闭服务但请求超时时会发生错误 1053。这会让人觉得 Tomcat 内部发生了一些事情,使它进入了一个非常糟糕的状态。我当然怀疑你给System.exit() 的电话可能是罪魁祸首。 Tomcat(特别是 Catalina)确实向 VM 注册了一个关闭挂钩(see org.apache.catalina.startup.Catalina.start(),至少在 5.0.30 中)。当您调用System.exit() 时,JVM 会调用该关闭挂钩。关闭钩子委托给正在运行的服务,因此每个服务都可能需要做很多工作。
如果关闭挂钩 (triggered by your System.exit()) 无法执行(它们会出现死锁或类似情况),那么很容易理解为什么会发生错误 1053,鉴于 Runtime.exit(int) 方法的文档(称为来自System.exit()):
如果在
虚拟机已开始关闭
序列然后如果关闭钩子是
正在运行此方法将阻塞
无限期地。如果关闭挂钩有
已经运行并退出
最终确定已启用
此方法暂停虚拟机
使用给定的状态码,如果
状态非零;否则,它
无限期阻塞。
这种“无限期阻塞”行为肯定会导致错误 1053。
如果你想要比这个更完整的答案,可以download the source自己调试。
但是,我敢打赌,如果您正确处理线程管理问题(如上所述),您的问题就会消失。
简而言之,将 System.exit() 调用留给 Tomcat - 这不是你的工作。