【问题标题】:Calling System.exit() in Servlet's destroy() method在 Servlet 的 destroy() 方法中调用 System.exit()
【发布时间】:2009-02-13 14:23:15
【问题描述】:

这是对我的earlier question 的跟进。

Tomcat 5.0.28 有一个错误,即容器在关闭时没有调用 Servlet 的 destroy() 方法。这在 Tomcat 5.0.30 中已修复,但如果 Servlet 的 destroy() 方法具有 System.exit(),则会导致 Tomcat Windows 服务抛出错误 1053 并拒绝正常关闭(有关更多详细信息,请参见上面的链接这个错误)

任何人都知道是否:

  • 在 Servlet 的 destroy() 方法中调用 System.exit() 来强制终止任何非守护线程是个好主意吗?

  • 如果 Servlet 的 destroy() 方法中有 System.exit(),为什么 Tomcat 5.0.30 和(包括 Tomcat 6.xx 在内的更高版本)无法正常关闭。

    李>

【问题讨论】:

    标签: java tomcat servlets windows-services


    【解决方案1】:

    在 Servlet 的 destroy() 方法中调用 System.exit() 来强制杀死任何非守护线程是个好主意吗?

    这绝对不是一个好主意 - 这是一个可怕的主意。 destroy() 方法在 servlet 停止服务时被调用,这可能由于多种原因而发生:servlet/webapp 已停止,webapp 正在取消部署,webapp 正在重新启动等。

    System.exit() 关闭整个 JVM!为什么要仅仅因为一个 servlet 正在卸载就强制关闭整个服务器?

    如果 Servlet 的 destroy() 方法中有 System.exit(),为什么 Tomcat 5.0.30 和(包括 Tomcat 6.x.x 的更高版本)无法正常关闭。

    大概是为了防止这样的危险行为吧。

    您不应编写假定您的代码/应用程序是服务器上唯一运行的代码的代码。

    【讨论】:

    • 好吧,我正在维护这段代码。此外,它可以保证这将是唯一在该 Tomcat 上运行的 Web 应用程序。
    • +1,在任何情况下都不应在 servlet 中调用 System.exit()。期间。
    • @Nikhil - 这并不能解释你为什么要这样做。你问这是否是一个好主意,答案是这是一个不好的做法。如果您能告诉我们您想要完成的工作,我们或许可以帮助您找到更好的解决方案?
    • 换句话说,这个“错误 1053”是不良做法的副作用,而不是问题的真正原因
    • 请参阅下面的答案,了解管理由 servlet 启动的线程的完整最佳实践模式。
    【解决方案2】:

    你问了两个问题:

    问题 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 - 这不是你的工作。

    【讨论】:

    • 我什至可以说 99.99% 的应用永远不需要调用 System.exit()。只需在 main 方法中跳出循环或从线程的 run() 方法中返回即可。
    • 同意,Shiny 先生。每当您看到对 System.exit() 的调用时,您都应该怀疑程序设计不佳。
    • 我认为 keepGoing 应该被标记为 volatile(或者它的访问是同步的),否则一些线程可能看不到新值。
    • System.exit(int) 如果你想为你的程序提供一个退出状态是必需的,就像基本上所有的命令行工具一样。
    • @Geoff - 是的。如果您正在编写需要操作退出代码的命令行实用程序,则需要调用 System.exit()。如果您需要从与 servlet 相关的进程中操作退出代码,我会严重质疑您的系统设计。 System.exit() 仅在您对整个系统有非常严格控制的情况下才有用 - servlet 不是那种情况 - 其他人(容器)负责系统,而不是您的 servlet。
    【解决方案3】:

    在一个内部调用 System.exit() Servlet 的 destroy() 方法 强行杀死任何非守护线程 是个好主意吗?

    不是个好主意。您将强制终止 所有 线程,其中可能包括当前正在关闭系统的 Tomcat 的一部分。这将导致 Tomcat 不正常地关闭。这也可以防止关闭处理程序运行,从而导致各种问题。

    为什么 Tomcat 5.0.30 和(之后 包括 Tomcat 6.x.x 的版本)失败 正确关机,如果有 destroy() 方法中的 System.exit() Servlet。

    很多代码在 Servlet 销毁后执行。 Context 销毁一个...其他 servlet 的所有其他侦听器。其他应用。雄猫本身。通过调用 System.exit,您可以阻止所有这些运行。

    更好的问题是这些非守护线程是什么,它们为什么运行,以及谁启动它们?

    【讨论】:

    • 回答您的最后一个问题,有很多线程可以执行各种任务,如轮询、报告等。 servlet 初始化这些线程,然后它们开始运行。
    【解决方案4】:

    在编写像 Jared 那样的线程关闭代码时,我通常将“keepGoing”成员和“stopExecuting()”方法设为静态,这样所有线程都会通过一次关闭调用获得信号以关闭。好主意还是不好?

    【讨论】:

    • 我对所有静态事物都有一种根深蒂固的厌恶——尤其是在处理并发时。这并不是说这种方法是错误的,或者它不起作用或效率不高。我对静态的厌恶可能是过度的。
    • 其实...在实践中,我通常会将 MyThread 实现为一个抽象类,doSomeStuff()、可能SleepForABit() 和 cleanup() 都是抽象的...因为静态不是继承的,所以行不通。
    • 编辑了我的答案以包含“servlet 线程管理”的抽象。
    • 我会保持 keepGoing var 非静态,因为有时您只想关闭一个线程。将所有线程句柄放在一个集合中并对其进行迭代并停止线程很容易。
    猜你喜欢
    • 2012-11-06
    • 1970-01-01
    • 2011-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-29
    • 1970-01-01
    • 2010-09-23
    相关资源
    最近更新 更多