如何在给定Future对象的情况下中断线程?

时间:2013-12-19 21:22:15

标签: java multithreading threadpool executorservice futuretask

如果未在5秒内完成,我想启动一个线程并取消它:

private final class HelloWorker implements Callable<String> {
    public String call() throws Exception {
        while(true) {
            if (Thread.isInterrupted()) {
                return null;
            }
        }
        return performExpensiveComputation();
    }

    private String performExpensiveComputation() {
        // some blocking expensive computation that may or may not take a very long time
    }
}

private ExecutorService executorService = Executors.newFixedThreadPool(threadPoolSize);
Future<String> future = executorService.submit(new HelloWorker());

try {  
    String s = future.get(5, TimeUnit.SECONDS);
} catch (TimeoutException e) {
    future.cancel(true);

    System.out.println("cancelled: " + future.isCancelled() + "done: " + future.isDone());

    executorService.shutdown();

    try {
        System.out.println("try to terminate: " + executorService.awaitTermination(60, TimeUnit.SECONDS));
    } catch (Exception ex) {
        // ignore
    }
}

然而,看起来awaitTermination返回false。有没有办法让我检查为什么ExecutorService不会终止?我可以找出仍在运行的线程吗?

2 个答案:

答案 0 :(得分:2)

为什么Future.cancel()无法按您认为的方式工作

将来取消将从正在运行的队列中删除任务。如果您的任务已经在运行,它将不会停止它。因此, cancel()是一个与中断不同的概念。正如Javadocs所说:

尝试取消执行此任务。如果此尝试将失败 任务已经完成,已经被取消或者可能 不会因为其他原因而被取消。如果成功了,这个任务 调用cancel时尚未启动,则该任务永远不会运行。如果 任务已经开始,那么mayInterruptIfRunning参数 确定执行此任务的线程是否应该 试图停止任务而中断。 https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/util/concurrent/Future.html#cancel(boolean)

您要问的是如何打断。幸运的是,当您调用 Future.cancel()时,它将调用中断方法。但是您需要允许它与mayInterruptIfRunning标志一起使用,并且需要正确处理中断(请参见下文)。

为什么要打扰?

当您需要长时间运行的任务现在需要停止时,或者当您有需要关闭的守护程序以及其他示例时,使用Java中的中断线程很有用。

如何打扰

要中断,请在线程上调用interrupt()。这是一个合作过程,因此您的代码必须为此做好准备。像这样:

myThread.interrupt();

负责的代码

您的代码的责任是为任何干扰做好准备。我想说的很久了,只要您有一个长期运行的任务,就插入一些中断准备好的代码,如下所示:

while (... something long...) {

     ... do something long

     if (Thread.interrupted()) {
         ... stop doing what I'm doing...
     }
}

如何停止我在做什么?

您有几种选择:

  1. 如果您位于 Runnable.run()中,只需返回或退出循环并完成该方法即可。
  2. 您可能正处于代码深处的其他某种方法中。此时,该方法可能会抛出 InterruptedException ,因此您只需这样做(将标志清除)即可。
  3. 但是也许在您的代码深处,抛出 InterruptedException 没有意义。在那种情况下,您应该抛出其他异常,但是在此之前,您的线程再次中断,因此捕获的代码知道正在进行中断。这是一个示例:
private void someMethodDeepDown() {
    while (.. long running task .. ) {
          ... do lots of work ...

          if (Thread.interrupted()) {
             // oh no! an interrupt!
             Thread.currentThread().interrupt();
             throw new SomeOtherException();
          }
     }
}

现在,异常可以传播终止线程或被捕获的异常,但是接收代码有望注意到正在进行中断。

我应该使用isInterrupted()还是interrupted()

您应该首选 interrupted(),因为:

  1. 您的代码应重置中断标志,因为如果您不使用所使用的线程,则可能返回到中断状态导致问题的线程池(当然,这是线程池代码中的错误,您不会例如,如果您使用 Executors.newFixedThreadPool(),则不会得到这种行为。但是其他线程代码也可以拥有它。
  2. 正如另一个回答所述,中断标志的清除表明您已收到消息并正在采取措施。如果您将其保留为true,则一段时间后的呼叫者会认为您不会及时对其进行响应。

为什么要使用interrupt()为什么在我的代码中不添加其他标志?

中断是最好的中断机制,因为我们的代码可以为之做好准备。如果我们发现只是捕获并忽略了 InterruptException 的代码,或者没有在其主体中检查 interrupted()的代码,则可以纠正这些错误,并使我们的代码始终清晰可中断无需在代码中创建对非标准机制的神秘依赖。

不幸的是,约书亚·布洛克(Joshua Block)在他的着名著作《有效Java,第二版》中提出了相反的说法。但是我相信启用 interrupt()方法可以按预期工作会更好。

答案 1 :(得分:1)

没有安全的方法来阻止正在运行的线程而不会影响其余进程的稳定性。这就是很久以前{@ 1}}被弃用的原因,也是Executor Services仅使用软协作Thread#stop机制的原因。

您的线程必须主动检查是否已请求中断并在结束前执行适当的清理。或者,线程将调用一些可中断的JDK方法,这些方法将抛出Thread#interrupt,胎面将正确地尊重并结束自己。