Трудно понять, когда выполняется ловушка завершения работы и как завершить работу ExecutorService. - PullRequest
2 голосов
/ 06 апреля 2019

Я пытаюсь запустить программу на основе scheduleAtFixedRate ExecutorService.Я использую этот сервис для замены фиктивной петли while.Глобальная идея состоит в том, чтобы сначала ExecutorService (планировщик) выполнял Runnable (работающий).тогда, по какой-то причине, исполняемый файл может или не может запланировать задачу на другом ExecutorService (исполнитель задач).Теперь возникает вопрос:

  • как мне остановить все эти потоки, когда я остановлю программу?

Я немного поэкспериментировал ине могу найти правильное решение.Я пробовал две вещи: DaemonThread, ShutdownHook Потоки демона - это не то, что я ищу, я хочу, чтобы планировщик продолжал работать, пока я не остановлю программу.На первый взгляд, ShutdownHook не был хорошим решением, но после некоторых исследований он, похоже, работает.

  • Я просто не понимаю, почему .

Дело в том, что ShutdownHook запускается, если я выполняю код с командой mvn exec:java -Dexec.mainClass="com.goodbook.App" и остановите его с помощью Ctrl+C, но он не будет выполнен, если я выполню код Java в своей IDE (VScode) и остановлю его с помощью инструмента отладчика.Итак, есть еще два вопроса:

  • Почему выполняется ShutdownHook?
  • Почему поведение при работе с командной строкой различнои работает с IDE

Сначала я дам вам два куска кода: класс App и Task.


App.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

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;
  }
}

Конец файла журнала в случае запуска с 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...

Конецфайл журнала в случае работы с 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

Мне действительно нужна помощь с этими проблемами, я не Java-разработчик: s

1 Ответ

1 голос
/ 06 апреля 2019

Отключающие крюки вызываются, когда JVM выключается;см. класс JavaDoc в Runtime.addShutdownHook.Но завершение запуска из отладчика - это , а не завершение работы.

Обычный выход

Когда выходит из последнего не потока демона или System.exit() вызывается с помощью кода или CTRL-C нажимается в командной строке (или программе отправляется сигнал об уничтожении, например, с помощью диспетчера задач), затем происходит выключениеперехваты выполняются, и после их завершения JVM выключается.

Выход из отладчика

Если вы прервете программу во время отладки и нажмете «Стоп», IDE немедленно прерывает ваше приложение .Нет никаких шансов запустить крюки отключения.Это обычно правильно делать;при отладке вы обычно хотите просто убить все, что работает.

изящно завершить работу во время отладки

Если вы хотите, чтобы во время отладки вы могли запускать ловушки завершения работы, естьряд способов;каждый звонит System.exit() через код;это вызовет крючки.В этом связанном вопросе они ждут события нажатия клавиши перед вызовом system.exit.


Относительно

как мне остановить все эти потоки при остановке программы?

снова, вы можете позвонить System.exit(), так что программа будет остановлена, даже если не-деамонные потоки находятся в середине выполнения (что на самом деле не чисто);но лучший способ - дать каждому потоку возможность прерваться в обычном режиме, например.если поток выполняет бесконечный цикл, он может периодически проверять переменную (volatile) shutdown и возвращать (через код), когда переменная становится установленной.(сама переменная shutdown должна быть установлена ​​с помощью ловушки отключения)

...