【问题标题】:Graceful shutdown of threads and executor优雅地关闭线程和执行器
【发布时间】:2010-07-26 07:02:27
【问题描述】:

以下代码尝试完成此操作。

代码永远循环并检查是否有任何待处理的请求需要处理。如果有,它会创建一个新线程来处理请求并将其提交给执行程序。所有线程完成后,它会休眠 60 秒并再次检查待处理的请求。

public static void main(String a[]){
    //variables init code omitted
    ExecutorService service = Executors.newFixedThreadPool(15);
    ExecutorCompletionService<Long> comp = new ExecutorCompletionService<Long>(service);
    while(true){
        List<AppRequest> pending = service.findPendingRequests();
        int noPending = pending.size();
        if (noPending > 0) {
            for (AppRequest req : pending) {
                Callable<Long> worker = new RequestThread(something, req);
                comp.submit(worker);
            }
        }
        for (int i = 0; i < noPending; i++) {
            try {
                Future<Long> f = comp.take();
                long name;
                try {
                    name = f.get();
                    LOGGER.debug(name + " got completed");
                } catch (ExecutionException e) {
                    LOGGER.error(e.toString());
                }
            } catch (InterruptedException e) {
                LOGGER.error(e.toString());
            }
        }
        TimeUnit.SECONDS.sleep(60);
    }

  }

我的问题是这些线程完成的大部分处理都与数据库有关。这个程序将在 Windows 机器上运行。当有人试图关闭或注销机器时,这些线程会发生什么?如何优雅地关闭正在运行的线程和执行器。?

【问题讨论】:

    标签: java multithreading


    【解决方案1】:

    ExecutorService 的典型有序关闭可能如下所示:

    final ExecutorService executor;
    
    Runtime.getRuntime().addShutdownHook(new Thread() {
        public void run() {
            executor.shutdown();
            if (!executor.awaitTermination(SHUTDOWN_TIME)) { //optional *
                Logger.log("Executor did not terminate in the specified time."); //optional *
                List<Runnable> droppedTasks = executor.shutdownNow(); //optional **
                Logger.log("Executor was abruptly shut down. " + droppedTasks.size() + " tasks will not be executed."); //optional **
            }
        }
    });
    

    *您可以记录执行程序在等待您愿意等待的时间后仍有任务要处理。
    **您可以尝试强制执行器的工作线程放弃其当前任务,并确保它们不会启动任何剩余的任务。

    请注意,当用户向您的java 进程发出中断或您的ExecutorService 仅包含守护线程时,上述解决方案将起作用。相反,如果 ExecutorService 包含尚未完成的非守护线程,则 JVM 不会尝试关闭,因此不会调用关闭挂钩。

    如果尝试将进程作为离散应用程序生命周期(而非服务)的一部分而关闭,则关闭代码不应放置在关闭挂钩内,而应放置在程序设计终止的适当位置。

    【讨论】:

    • 这是倒退的。假设ExecutorServiceExecutors 工厂方法返回的典型值之一,则支持线程是非守护线程。在这些线程退出之前,不会调用关闭挂钩,并且在关闭 ExecutorService 之前不会发生这种情况。仅当执行程序的线程是非守护程序或您的程序收到用户中断时,您的解决方案才会这样做。
    • OP 正在特别询问 JVM 被要求执行有序关闭的情况。无论如何,答案的内容是准确的,只需不需要在关闭挂钩中执行此操作即可提前终止执行程序服务。
    • 感谢您这么快回答。我想我错过了他们问题的那一部分。你能添加第三个*** 来澄清这一点吗?
    • 请随意编辑。您有一个关于计划终止程序而不是服务的有效观点。
    • 我更喜欢sout,因为那时log4j可能会被卸载
    【解决方案2】:

    Java Concurrency in Practice》一书指出:

    7.4。 JVM 关闭

    JVM 可以在以下任一情况下关闭 有序或突然的方式。有条不紊 关机是在最后一次启动时 “正常”(nondaemon)线程 终止,有人调用 System.exit, 或通过其他特定于平台的方式 (例如发送 SIGINT 或点击 Ctrl-C)。 [...]

    7.4.1。关闭挂钩

    在有序关闭中,JVM首先 启动所有已注册的关闭挂钩。 关闭挂钩是未启动的线程 注册的 Runtime.addShutdownHook。 JVM 使 不保证顺序 关闭挂钩已启动。如果有的话 应用程序线程(守护进程或 nondaemon) 仍在运行 关机时间,它们继续运行 与关机同时 过程。当所有关闭挂钩都有 完成后,JVM可以选择运行 如果 runFinalizersOnExit 是终结器 真,然后停止。 JVM 没有 试图停止或打断任何 仍然存在的应用程序线程 在关机时运行;他们是 当JVM突然终止 最终停止。如果关机 挂钩或终结器未完成, 然后是有序的关机过程 “挂起”并且必须关闭 JVM 突然。 [...]

    重要的是, “JVM 不会尝试停止或中断任何在关闭时仍在运行的应用程序线程;当 JVM 最终停止时,它们会突然终止。” 所以我想与 DB 的连接会突然终止,如果没有关闭挂钩来进行优雅的清理(如果您使用的是框架,它们通常会提供这样的关闭挂钩)。以我的经验,与数据库的会话可以一直保持,直到应用程序被数据库超时等。没有这样的钩子就被终止了。

    【讨论】:

      【解决方案3】:

      由于添加关闭挂钩来显式调用 shutdown() 对我不起作用,我在 Google 的 Guava 中找到了一个简单的解决方案: com.google.common.util.concurrent.MoreExecutors.getExitingExecutorService.

      【讨论】:

      • 不过,这还是有点问题,因为 MoreExecutors.getExitingExecutorService 只接受 ThreadPoolExecutor 实例。因此,您必须手动定义这些执行程序,而不是简单地调用,例如,返回 ExecutorServiceExecutors.newFixedThreadPool
      【解决方案4】:

      您可以拨打shutdown()ExecutorService

      启动有序关闭,其中 之前提交的任务是 执行,但不会有新任务 接受。

      或者您可以拨打shutdownNow():

      尝试主动停止所有 执行任务,停止处理 等待任务,并返回一个列表 正在等待的任务 执行。

      没有任何保证 尽最大努力阻止 处理积极执行的任务。 例如,典型的实现 将通过 Thread.interrupt() 取消,所以 任何无法响应的任务 中断可能永远不会终止。

      你调用哪一个取决于你希望它停止的程度......

      【讨论】:

      【解决方案5】:

      我有类似的问题,我曾经得到类似的错误

      o.a.c.loader.WebappClassLoaderBase :: Web 应用程序 [ROOT] 似乎已经启动了一个名为 [pool-2-thread-1] 的线程,但未能停止它。这很可能造成内存泄漏。线程的堆栈跟踪: sun.misc.Unsafe.park(本机方法) java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)

      下面的代码修复了它

      private ThreadPoolExecutor executorPool;
      
      @PostConstruct
      public void init() {
          log.debug("Initializing ThreadPoolExecutor");
          executorPool = new ThreadPoolExecutor(1, 3, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1));
      }
      
      @PreDestroy
      public void destroy() {
          log.debug("Shuting down ThreadPoolExecutor");
          executorPool.shutdown();
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-08-22
        • 1970-01-01
        相关资源
        最近更新 更多