当Tomcat关闭时,如何正确关闭线程?

5
我正在尝试在关闭Tomcat时关闭线程。具体来说,我正在尝试关闭log4j watchdog(用于文件更改),并且我还尝试关闭使用我的Web应用程序中的类的执行器。关闭时,我在Catalina.out中看到异常。对于Log4J,我看到:
INFO:非法访问:此Web应用程序实例已停止。无法加载org.apache.log4j.helpers.NullEnumeration。以下最终堆栈跟踪是由于为了调试目的而抛出错误以及尝试终止导致非法访问的线程,并且没有功能影响。Throwable发生了: java.lang.IllegalStateException 在org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1587) 在org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1546) 在org.apache.log4j.Category.getAllAppenders(Category.java:413) 在org.apache.log4j.Category.closeNestedAppenders(Category.java:226) 在org.apache.log4j.Hierarchy.shutdown(Hierarchy.java:467) 在org.apache.log4j.LogManager.shutdown(LogManager.java:267) 在com.listeners.myListener $ 1.run(myListener.java:232) 线程“Thread-14”中的异常java.lang.NoClassDefFoundError: org.apache.log4j.helpers.NullEnumeration 在org.apache.log4j.Category.getAllAppenders(Category.java:413) 在org.apache.log4j.Category.closeNestedAppenders(Category.java:226) 在org.apache.log4j.Hierarchy.shutdown(Hierarchy.java:467) 在org.apache.log4j.LogManager.shutdown(LogManager.java:267)
对于执行器部分:
INFO:非法访问:此Web应用程序实例已停止。无法加载com.my.class.SomeClass。以下最终堆栈跟踪是由于为了调试目的而抛出错误以及尝试终止导致非法访问的线程,并且没有功能影响。Throwable发生了: java.lang.IllegalStateException 在org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1587) 在org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1546) 在 线程“Thread-13”中的异常java.lang.NoClassDefFoundError: com.my.class.SomeClass
我在ServletContextListener上的contextDestroyed中添加了关闭挂钩,如下所示:
public void contextDestroyed(ServletContextEvent arg0) {  

         Runtime.getRuntime().addShutdownHook(new Thread(){  
            @Override  
            public void run(){  
                LogManager.shutdown();                  
            }  
         });  

    } 





 public void contextDestroyed(ServletContextEvent arg0) {  

        Runtime.getRuntime().addShutdownHook(new Thread(){  
            @Override  
            public void run(){  
                SomeClass.updater.shutdown();  
            }  
        });  

    }  

我在这里做错了什么?为什么会出现异常?

更新:
SomeClass.updater 是一个 public static ScheduledExecutorService
LogManagerorg.apache.log4j.LogManager

更新2:
按照 BGR 的答案,我直接执行如下操作:

public void contextDestroyed(ServletContextEvent arg0) {  

            SomeClass.updater.shutdown();  

        }  

并且

public void contextDestroyed(ServletContextEvent arg0) {  

                LogManager.shutdown();                  

        } 

我没有从Log4j中得到异常,但是我收到了以下关于SomeClass.updater的异常信息,它是一个public static ScheduledExecutorService

INFO: 非法访问:此Web应用程序实例已停止。无法加载java.util.concurrent.ExecutorService。最终的跟踪堆栈由于为调试目的而抛出错误并尝试终止导致非法访问的线程,因此没有功能影响。Throwable发生:
java.lang.IllegalStateException
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1587)
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1546)

为什么会这样呢?这些类已经被垃圾回收了吗?
2个回答

3
我会在servlet的init()方法中注册关闭钩子(shutdown hooks),而不是在contextDestroyed()方法中注册。但无论如何,为什么您需要shutdown hooks呢?
您不能直接在contextDestroyed()方法中调用SomeClass.updater.shutdown();吗? 编辑 监听器的contextDestroyed()对于执行器服务来说太晚了。根据javadoc中的说明,在通知任何ServletContextListeners上下文销毁之前,所有servlet和过滤器都将被销毁
然而,重写servlet的destroy()应该没问题,因为根据javadoc的说明,此方法提供了一个机会,让servlet清理任何正在占用的资源(例如内存、文件句柄、 线程 ...
@Override
public void destroy(  ) {


        myThreadExecutor.shutdown();

        super.destroy(  );
}

我考虑使用关闭钩子,以确保在JVM关闭时它们被调用。 - Jim
相信你的容器 :-). 无论如何,尽早注册它们;我会选择servlet init()方法。 - Bruno Grieder
Jim,你能否直接在Servlet的destroy()方法中调用SomeClass.updater.shutdown();(而不是在监听器中,因为那时已经太晚了)根据destroy()方法的javadoc,此方法为servlet提供了清理正在持有的任何资源(例如内存、文件句柄、线程)并确保任何持久状态与servlet在内存中的当前状态同步的机会。 - Bruno Grieder
据我所知,destroy()方法不能保证完全执行。也就是说,Tomcat会调用destroy()方法,然后很快终止,而不考虑destroy()是否已经完成。 - Don Cheadle

2

调用

LogManager.shutdown();

在contextDestroyed()方法中是第一步,但ExecutorService不会立即关闭。您之所以会收到异常,是因为在contextDestroyed()方法返回后,ExecutorService线程仍在运行。您需要执行以下操作:

public void contextDestroyed(ServletContextEvent arg0) {  
    LogManager.shutdown();
    if(LogManager.awaitTermination(10, TimeUnit.SECONDS) == false) {
        LogManager.shutdownNow();
    }
} 

这样一来,当contextDestroyed()退出时,线程池就关闭并停止了所有线程。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接