Почему поток, запущенный ScheduledExecutorService.schedule(), никогда не заканчивается?

Когда я создаю поток, вызывая ScheduledExecutorService.schedule(), он никогда не заканчивается после выполнения запланированной задачи.

Например, следующая программа никогда не завершает работу:

public static void main(String[] args) {
  ScheduledFuture scheduledFuture = 
      Executors.newSingleThreadScheduledExecutor().schedule(new Callable() {

    public Void call() {
      doSomething();
      return null;
    }

  }, 1, TimeUnit.SECONDS);
}

public static void doSomething() {
}

Является ли это ошибкой JDK, или я просто что-то пропустил?

Ответ 1

Запланированная задача либо выполняется, либо ожидает выполнения.

Если задача ожидает выполнения, future.cancel() предотвратит ее выполнение (оба отменят (true)/cancel (false)).

Если задача уже выполняется, future.cancel(false) не будет иметь эффекта. future.cancel(true) прервет поток, выполняющий эту задачу. Будет ли это иметь какой-либо эффект, зависит от вас, который выполнит эту задачу. Задача может или не может реагировать на прерывание в зависимости от реализации.

Чтобы ваша задача реагировала на отмену, вы должны реализовать doSomething(), чтобы она реагировала на прерывание.

В основном есть два способа сделать это:

1. Проверьте флаг прерывания в вашей логике

public void doSomething(){
    stuff();
    //Don't use Thread.interrupt()
    if(Thread.currentThread().isInterrupted()){
        // We have an interruption request, possibly a cancel request
        //Stop doing what you are doing and terminate.
        return;
    }
    doLongRunningStuff();
}  

Вы должны время от времени проверять флаг прерывания, а если прервать, прекратите то, что вы делаете, и верните. Обязательно используйте Thread.isInterrupted(), а не Thread.interrupt() для проверки.

2.Act при прерванном исключении

public void doSomething(){
    try{
        stuff();
    }catch(InterruptedException e){
        // We have an interruption request, possibly a cancel request

        // First, preserve Interrupted Status because InterruptedException clears the 
        // interrupted flag
        Thread.currentThread.interrupt();

        // Now stop doing your task and terminate
        return;
    }

    doLongRunningStuff();
}

Если у вас есть какой-либо метод, который выдает InterruptedException, обязательно остановите свое действие и завершите его, когда вы его выбросите.

Как только вы реализуете свои методы таким образом, вы можете вызвать future.cancel(true), чтобы отменить выполнение выполняемой задачи.

Ответ 2

Ваша программа никогда не заканчивается, потому что вы создаете ScheduledExecutorService, который содержит пул потоков, но вы никогда не закрываете эту службу. Поэтому пользовательские потоки в пуле потоков никогда не завершаются, и, следовательно, виртуальная машина продолжает работать вечно.

Чтобы решить эту проблему, вам нужно вызвать shutdown() в службе исполнителя. Это можно сделать даже после планирования задачи, которую вы хотите выполнить:

public static void main(String[] args) {
  ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
  ScheduledFuture scheduledFuture = executorService.schedule(new Callable() {

    public Void call() {
      doSomething();
      return null;
    }

  }, 1, TimeUnit.SECONDS);
  executorService.shutdown();
}

Это нормально выполнит запланированную задачу, а затем прекратит поток в пуле.

Ответ 3

Вам нужно позвонить scheduledExecutorService.shutdown(), чтобы остановить выполнение. В противном случае он перезапускается каждую секунду.

(EDITED: см. комментарии)