问题 为什么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错误,还是我错过了什么?


3187
2018-05-25 09:15


起源

在doSomething()中发生了什么? - Boris Pavlović


答案:


计划任务正在执行或正在等待执行。

如果任务等待执行, future.cancel() 将阻止它被执行(取消(true)/取消(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 on Interrupted exception

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)来取消正在运行的任务的执行。


7
2018-05-25 13:39



如何在调用命令后关闭调度?以便释放线程资源。毕竟,由schedule创建的任务执行一次。 - Nickolas
@Nickolas:不确定你的意思,但是如果你的意思是一个计划运行一次的任务(在延迟等之后)并且它已经完成,那么任务就已经停止了(并释放了线程)。否则我的答案仍然适用。 - Enno Shioji
对于另一个问题,这是一个很好的答案。问题不在于如何终止任务 - OP明确声明任务是一个nop - 但是如何终止用于运行任务的线程。 - oberlies


您的程序永远不会终止,因为您创建了一个 ScheduledExecutorService,它拥有一个线程池,但你永远不会关闭该服务。因此,线程池中的用户线程永远不会终止,因此VM将永远运行。

要解决这个问题,你需要调用一个 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();
}

这将正常执行计划任务,然后终止池中的线程。


5
2018-04-29 16:25





你需要打电话 scheduledExecutorService.shutdown() 停止执行否则每秒重启一次。

(已编辑:见评论)


2
2018-05-25 09:21



应调用scheduledExecutorService.shutdown()而不是scheduleFuture.shutdown(),而ScheduledExecutorService.schedule()调用是一次性操作,永远不会再次重新启动。 - moonese