2

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

4

2 回答 2

4

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

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

于 2013-12-19T21:30:04.630 回答
3

为什么 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()方法按预期工作要好得多。

于 2020-06-17T22:00:33.910 回答