ScheduledExecutorService.schedule()启动的线程为什么永远不会终止?

13

当我通过调用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的bug吗,还是我漏掉了什么?


doSomething() 函数里发生了什么? - Boris Pavlović
3个回答

7

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

如果任务正在等待执行,future.cancel()将阻止其执行(无论是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.处理InterruptedException异常

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


如何在调用命令后关闭计划任务,以释放线程资源。毕竟,由计划创建的任务只执行一次。 - Nickolas
@Nickolas:不确定你的意思,但如果你指的是一个被安排在延迟后运行一次的任务已经完成了,那么这个任务已经停止了(并释放了线程)。否则我的回答仍然适用。 - Enno Shioji
1
这是一个不错的回答,但是针对的是另一个问题。问题不在于如何终止任务——OP明确表示该任务是nop——而在于如何终止用于运行任务的线程。 - oberlies

5
您的程序永远不会终止,因为您创建了一个 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();
}

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


1
你需要调用 scheduledExecutorService.shutdown() 来停止执行。否则它会每秒重新启动一次。
(编辑:请参见评论)

2
应该调用scheduledExecutorService.shutdown()而不是scheduleFuture.shutdown(),而ScheduledExecutorService.schedule()方法是单次执行的,不会再次重启。 - moonese

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接