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" 事件,因此它甚至不会尝试关闭其执行程序。