【问题标题】:How do I interrupt a thread given a Future object?如何中断给定 Future 对象的线程?
【发布时间】:2014-01-08 15:01:06
【问题描述】:

我想启动一个线程,如果它没有在 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 不会终止?我可以弄清楚哪些线程仍在运行吗?

【问题讨论】:

  • future.cancel(true) 实际上会中断线程。但这只会打开thread.isInterrupted() 标志。您需要对其进行测试或注意您的InterruptedExceptions。
  • 有什么方法可以调用 thread.stop()?
  • thread.stop() 已弃用。请参阅@Marko 的回答。
  • @Popcorn 你不需要调用 Thread.stop() 你需要在 performExpensiveComputation() 中检查 Thread.interrupted() (或者 isInterrupted() 见我的长答案)

标签: java multithreading threadpool executorservice futuretask


【解决方案1】:

没有安全的方法可以在不影响进程其余部分的稳定性的情况下停止正在运行的线程。这就是为什么 Thread#stop 很久以前就被弃用了,以及为什么 Executor Services 只使用软的、合作的 Thread#interrupt 机制。

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

【讨论】:

  • +1 你用while (!Thread.currentThread().isInterrupted()))之类的东西检查中断位。
  • 如果我的线程执行的计算是阻塞的怎么办?在这种情况下,我如何主动检查中断?我已经编辑了我的 HelloWorker 以显示一个示例。
  • 这是一个经典问题:你不能一边吃蛋糕一边吃。如果线程被强行停止,则没有进程隔离来保护所有其他线程免受损坏。如果您的长时间运行的计算方法不能更改为偶尔检查中断标志,那么您将无法安全地停止线程。
  • 或者,如果您确定计算不会对 JVM 的其余部分产生任何不良影响,您可以使用原始 Thread 并使用 stop 杀死它。跨度>
【解决方案2】:

为什么 Future.cancel() 不像你想象的那样工作

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

尝试取消此任务的执行。此尝试将失败,如果 任务已经完成、已经被取消或可能 不会因为其他原因取消。如果成功了,这个任务 调用取消时尚未开始,此任务不应运行。如果 任务已经开始,然后是 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,过一段时间来电者可能会认为您不会及时回复。

为什么要在我的代码中添加一些其他标志?

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

不幸的是,Joshua Block 在他的名著Effective Java, Second Edition中提出了相反的观点。但我相信让 interrupt() 方法按预期工作要好得多。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-10-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-01
    • 1970-01-01
    • 2012-04-08
    • 2023-01-31
    相关资源
    最近更新 更多