ExecutorService 在停止时不会从 contextDestroyed() 关闭 Tomcat
ExecutorService doesn't shut down from contextDestroyed() when stopping Tomcat
我有一个 ExecutorService executor = Executors.newSingleThreadExecutor();
我想在服务器关闭时停止。
我有一个 class implements ServletContextListener
并且用 @WebListener
注释。
我有两个方法 class:
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("ServletContextListener started");
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
executor.shutdown();
executor.shutdownNow();
System.out.println("ServletContextListener destroyed");
}
而且我看到它按预期打印了它们中的内容,但是当我在 intelij 中按一次停止按钮时,我得到:
SEVERE: The web application [] appears to have started a thread named [pool-2-thread-1] but has failed to stop it. This is very likely to create a memory leak.
在它打印 ServletContextListener destroyed
之后。
我需要再次按下停止按钮才能完全停止它。
为什么即使达到 executor.shutdown();
也没有关闭 ExecutorService?我做错了什么?
PS: 这是我唯一的 ExecutorService,我没有创建其他线程。
编辑 2:
执行器服务是单例class中的一个字段,它用class初始化:
private ExecutorService executor = Executors.newSingleThreadExecutor();
这是 class 的初始化方式(惰性初始化):
public static RoomsManager getRoomsManager(ServletContext servletContext) {
if (servletContext.getAttribute(MANAGER_GAMES_ATTRIBUTE_NAME) == null) {
servletContext.setAttribute(MANAGER_GAMES_ATTRIBUTE_NAME, new RoomsManager());
}
return (RoomsManager)servletContext.getAttribute(MANAGER_GAMES_ATTRIBUTE_NAME);
}
并且是这样注释的:
@WebListener
public class RoomsManager implements ServletContextListener {
停止按钮是intelij IDEA中播放和调试按钮附近的红色方块。
这样做有效:
class YourThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
return new Thread(r, "Your name");
}
}
private ExecutorService executor = Executors.newSingleThreadExecutor(new YourThreadFactory());
因为显然,tomcat 的线程是守护进程,因此,当它们使用 return new Thread(r, "Your name");
创建新线程时,它也成为守护进程。
但是在执行程序服务使用的 DefaultThreadFactory 中,我看到它确保关闭新线程的守护进程。
这并不能解释为什么 executor.shutdown();
不起作用,但现在至少它可以正常关闭。
问题是您有两个不同的 RoomsManager
实例(因此,有两个不同的执行程序):第一个是由 Tomcat 创建的,第二个是由您创建的。
当您使用 @WebListener
注释 RoomsManager
时,Tomcat 会自动创建该 class 的实例并订阅它以接收 servlet 上下文 create/destroy 事件。该实例是实际停止其执行程序并打印 ServletContextListener destroyed
.
的实例
第二个实例是您在 getRoomsManager
方法中创建的(顺便说一下,该方法看起来不是线程安全的)。该实例未在 Tomcat 中注册并且未接收到 servlet 上下文 "destroy" 事件,因此它甚至不会尝试关闭其执行程序。
我有一个 ExecutorService executor = Executors.newSingleThreadExecutor();
我想在服务器关闭时停止。
我有一个 class implements ServletContextListener
并且用 @WebListener
注释。
我有两个方法 class:
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("ServletContextListener started");
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
executor.shutdown();
executor.shutdownNow();
System.out.println("ServletContextListener destroyed");
}
而且我看到它按预期打印了它们中的内容,但是当我在 intelij 中按一次停止按钮时,我得到:
SEVERE: The web application [] appears to have started a thread named [pool-2-thread-1] but has failed to stop it. This is very likely to create a memory leak.
在它打印 ServletContextListener destroyed
之后。
我需要再次按下停止按钮才能完全停止它。
为什么即使达到 executor.shutdown();
也没有关闭 ExecutorService?我做错了什么?
PS: 这是我唯一的 ExecutorService,我没有创建其他线程。
编辑 2:
执行器服务是单例class中的一个字段,它用class初始化:
private ExecutorService executor = Executors.newSingleThreadExecutor();
这是 class 的初始化方式(惰性初始化):
public static RoomsManager getRoomsManager(ServletContext servletContext) {
if (servletContext.getAttribute(MANAGER_GAMES_ATTRIBUTE_NAME) == null) {
servletContext.setAttribute(MANAGER_GAMES_ATTRIBUTE_NAME, new RoomsManager());
}
return (RoomsManager)servletContext.getAttribute(MANAGER_GAMES_ATTRIBUTE_NAME);
}
并且是这样注释的:
@WebListener
public class RoomsManager implements ServletContextListener {
停止按钮是intelij IDEA中播放和调试按钮附近的红色方块。
这样做有效:
class YourThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
return new Thread(r, "Your name");
}
}
private ExecutorService executor = Executors.newSingleThreadExecutor(new YourThreadFactory());
因为显然,tomcat 的线程是守护进程,因此,当它们使用 return new Thread(r, "Your name");
创建新线程时,它也成为守护进程。
但是在执行程序服务使用的 DefaultThreadFactory 中,我看到它确保关闭新线程的守护进程。
这并不能解释为什么 executor.shutdown();
不起作用,但现在至少它可以正常关闭。
问题是您有两个不同的 RoomsManager
实例(因此,有两个不同的执行程序):第一个是由 Tomcat 创建的,第二个是由您创建的。
当您使用 @WebListener
注释 RoomsManager
时,Tomcat 会自动创建该 class 的实例并订阅它以接收 servlet 上下文 create/destroy 事件。该实例是实际停止其执行程序并打印 ServletContextListener destroyed
.
第二个实例是您在 getRoomsManager
方法中创建的(顺便说一下,该方法看起来不是线程安全的)。该实例未在 Tomcat 中注册并且未接收到 servlet 上下文 "destroy" 事件,因此它甚至不会尝试关闭其执行程序。