简体   繁体   English

难以理解何时执行关闭挂钩以及如何终止 ExecutorService

[英]Difficulty to understand when is shutdown hook executed and how to terminate an ExecutorService

I'm trying to run a program based on a scheduleAtFixedRate ExecutorService .我正在尝试运行基于scheduleAtFixedRate ExecutorService的程序。 I use this service to replace a dummy while loop.我使用此服务来替换一个虚拟的while循环。 The global idea is to have first an ExecutorService (the scheduler) executing a Runnable (the runnable).全局的想法是首先让一个ExecutorService (调度程序)执行一个Runnable (runnable)。 then, for some reason, the runnable may or may not schedule a task on an other ExecutorService (the tasker).然后,出于某种原因,可运行对象可能会或可能不会在其他ExecutorService (任务程序)上安排任务。 Now the question is :现在的问题是:

  • how am I suppose to stop all these Threads when I stop the program ?当我停止程序时,我该如何停止所有这些线程?

I have experimented a bit and can't find a proper solution.我已经尝试了一点,但找不到合适的解决方案。 I have tried two things : DaemonThread , ShutdownHook Daemon threads are not what I'm looking for, I want the scheduler to keep running until I stop the program.我尝试了两件事: DaemonThreadShutdownHook守护线程不是我要找的,我希望调度程序继续运行直到我停止程序。 A ShutdownHook was not a good solution at first sight but after some researches it seems to work.乍一看,ShutdownHook 并不是一个好的解决方案,但经过一些研究,它似乎有效。

  • I just don't understand why .我只是不明白为什么

The thing is, the ShutdownHook is runned if I execute the code with the command mvn exec:java -Dexec.mainClass="com.goodbook.App" and stop it with Ctrl+C , but it's not executed if I run the java code in my IDE (VScode) and stop it with the debugger tool.问题是,如果我使用命令mvn exec:java -Dexec.mainClass="com.goodbook.App"执行代码并使用Ctrl+C停止它,则会运行mvn exec:java -Dexec.mainClass="com.goodbook.App" ,但如果我运行 java 代码,它不会执行在我的 IDE (VScode) 中并使用调试器工具停止它。 So there are two more question :所以还有两个问题:

  • Why is the ShutdownHook executed ?为什么执行ShutdownHook
  • Why is the behavior diffrent between running with the command line and running with the IDE为什么使用命令行运行和使​​用 IDE 运行之间的行为不同

First I will give you two pieces of code : the App and the Task class.首先我会给你两段代码:App 和 Task 类。


App.java应用程序.java

package com.goodbook;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

class ShutdownHook {
  private static final Logger logger = LoggerFactory.getLogger(ShutdownHook.class);
  public void attachShutDownHook(ScheduledExecutorService scheduler, ScheduledExecutorService tasker) {
    logger.info("attaching shutdown hook");
    Runtime.getRuntime().addShutdownHook(new Thread() {
      @Override
      public void run() {
        scheduler.shutdown();
        tasker.shutdown();
        logger.info("shutdown hook runned");
        logger.info("scheduler is down : "+scheduler.isShutdown());
        logger.info("tasker is down : "+tasker.isShutdown());
      }
    });
  }
}

public class App 
{
  private static final Logger logger = LoggerFactory.getLogger(App.class);
  public static void main( String[] args )
  {   
    logger.info("starting program");
    ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(0);
    ScheduledExecutorService tasker = Executors.newScheduledThreadPool(0);

    ShutdownHook shutdown = new ShutdownHook();
    shutdown.attachShutDownHook(scheduler, tasker);

    Task t = new Task("task1");

    Runnable  runnable = () -> {
      logger.info("running Tasker 1");
      if(!t.isPlanified()){
        logger.info("unplanified task found "+t.getName());
        logger.info("planning...");
        tasker.schedule(t, 1000, TimeUnit.MILLISECONDS);
        t.setPlanified(true);
      }
    };
    logger.info("scheduling tasks");
    scheduler.scheduleAtFixedRate(runnable, 0, 1000, TimeUnit.MILLISECONDS);
  } 
}

Task.java任务.java

package com.goodbook;

import java.util.concurrent.atomic.AtomicBoolean;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Task implements Runnable {
  private static final Logger logger = LoggerFactory.getLogger(App.class);

  String name;
  AtomicBoolean planified = new AtomicBoolean(false);

  public Task(String name){
    this.name = name;
  }

  @Override
  public void run(){
    logger.info(name+" run");
    planified.set(false);
  }

  public Boolean isPlanified(){
    return planified.get();
  }

  public void setPlanified(Boolean b){
    planified.set(b);
  }

  public String getName(){
    return name;
  }
}

The end of the log file in the case of running with VScode and stopping execution with the debugger tool:用VScode运行,用调试器工具停止执行的情况下日志文件的结尾:

INFO    2019-04-06 16:35:54,121 [pool-1-thread-1] com.goodbook.App  - planning...
INFO    2019-04-06 16:35:55,121 [pool-1-thread-1] com.goodbook.App  - Running Tasker 1
INFO    2019-04-06 16:35:55,121 [pool-2-thread-4] com.goodbook.TestTask  - task1 run
INFO    2019-04-06 16:35:56,121 [pool-1-thread-1] com.goodbook.App  - Running Tasker 1
INFO    2019-04-06 16:35:56,121 [pool-1-thread-1] com.goodbook.App  - Unplanified task found task1
INFO    2019-04-06 16:35:56,121 [pool-1-thread-1] com.goodbook.App  - planning...

The end of the log file in the case of running with mvn exec:java -Dexec.mainClass="com.goodbook.App" and stopping execution with Ctrl+C :在使用mvn exec:java -Dexec.mainClass="com.goodbook.App"并使用Ctrl+C停止执行的情况下,日志文件的结尾:

INFO    2019-04-06 16:59:09,686 [pool-1-thread-1] com.goodbook.App  - running Tasker 1
INFO    2019-04-06 16:59:09,688 [pool-2-thread-1] com.goodbook.Task  - task1 run
INFO    2019-04-06 16:59:10,686 [pool-1-thread-1] com.goodbook.App  - running Tasker 1
INFO    2019-04-06 16:59:10,686 [pool-1-thread-1] com.goodbook.App  - unplanified task found task1
INFO    2019-04-06 16:59:10,687 [pool-1-thread-1] com.goodbook.App  - planning...
INFO    2019-04-06 16:59:11,686 [pool-1-thread-1] com.goodbook.App  - running Tasker 1
INFO    2019-04-06 16:59:11,687 [pool-2-thread-2] com.goodbook.Task  - task1 run
INFO    2019-04-06 16:59:12,641 [Thread-1] com.goodbook.ShutdownHook$1  - shutdown hook runned
INFO    2019-04-06 16:59:12,642 [Thread-1] com.goodbook.ShutdownHook$1  - scheduler is down : true
INFO    2019-04-06 16:59:12,642 [Thread-1] com.goodbook.ShutdownHook$1  - tasker is down : true

I really need help with these problems, I'm not a java dev :s我真的需要帮助解决这些问题,我不是 Java 开发人员:s

Shutdown hooks are called when the JVM is shutting down; Shutdown hooks 在JVM关闭时被调用; see the class JavaDoc at Runtime.addShutdownHook .请参阅Runtime.addShutdownHook JavaDoc 类。 But terminating a run from a debugger is not a shutdown.但是从调试器终止运行并不是关闭。

Usual exit通常退出

When either the last non- daemon thread exits, or System.exit() is called via code, or CTRL-C is pressed on the command line, (or the program is sent a kill signal, eg. using task manager), then the shutdown hooks are executed, and once they have completed, the JVM shuts down.当最后一个非守护线程退出时,或者System.exit()通过代码调用,或者在命令行上按下CTRL-C ,(或者程序被发送一个终止信号,例如使用任务管理器),然后关闭钩子被执行,一旦它们完成,JVM 就会关闭。

Exit from debugger退出调试器

If you terminate the program during debugging, and press "stop", the IDE aborts your app immediately .如果您在调试期间终止程序并按“停止”, IDE 会立即中止您的应用程序 There is no chance to run the shutdown hooks.没有机会运行关闭挂钩。 This is usually the right thing to do;这通常是正确的做法; when debugging, you normally want to just kill whatever is running.在调试时,您通常只想杀死正在运行的任何东西。

Exiting gracefully while debugging调试时优雅退出

If you want a way to run the shutdown hooks while debugging, there are a number of ways;如果您想在调试时运行关闭挂钩,有多种方法; one is calling System.exit() via code;一种是通过代码调用System.exit() that will trigger the hooks.这将触发钩子。 In this related question, they wait for a keypress event before calling system.exit.这个相关问题中,他们在调用 system.exit 之前等待按键事件。


Regarding关于

how am I supposed to stop all these Threads when I stop the program ?当我停止程序时,我应该如何停止所有这些线程?

again, you can call System.exit() so that the program will terminate even if non-deamon threads are in the middle of an execution (which is not really clean);再次,您可以调用System.exit()以便程序将终止,即使非守护线程处于执行过程中(这不是很干净); but a better way is to give every thread a chanche to terminate normally- eg.但更好的方法是给每个线程一个正常终止的机会 - 例如。 if a thread is running an infinite loop, it could periodically check a (volatile) shutdown variable, and return (via code) when the variable becomes set.如果线程正在运行无限循环,它可以定期检查(易失性) shutdown变量,并在变量设置时(通过代码)返回。 (the shutdown variable itself should be set by a shutdown hook) shutdown变量本身应该由关闭钩子设置)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM