java 如何中断一个给定Future对象的线程?

uujelgoq  于 2023-01-07  发布在  Java
关注(0)|答案(2)|浏览(323)

我想启动一个线程,如果它没有在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不会终止?我能找出哪些线程还在运行吗?

jv2fixgn

jv2fixgn1#

没有一种安全的方法可以在不影响进程其余部分稳定性的情况下停止正在运行的线程,这就是为什么Thread#stop很久以前就被弃用了,也是为什么Executor Services只使用软的、协作的Thread#interrupt机制。
你的线程必须主动检查是否有中断请求,并在结束前进行适当的清理。或者,线程将调用一些可中断的JDK方法,这将抛出InterruptedException,线程将正确地荣誉并结束自己。

pbpqsu0x

pbpqsu0x2#

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

Future cancel从运行队列中删除任务。如果你的任务已经在运行,它不会停止它。所以**cancel()是一个不同于中断的概念。正如Javadoc所说:
尝试取消此任务的执行。如果任务已完成、已取消或由于某些其他原因无法取消,则此尝试将失败。如果成功,并且在调用取消时此任务尚未启动,则此任务将永远不会运行。如果任务已启动,则mayInterruptIfRunning参数确定是否应中断执行此任务的线程以尝试停止该任务。https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/util/concurrent/Future.html#cancel(布尔值)
你要问的是如何中断。幸运的是,当你调用
Future. cancel()**时,它会调用interrupt方法。但是你需要用mayInterruptIfRunning标志来允许它,并且需要正确地处理中断(见下文)。
"为什么要打断我"
在Java中,当您有一个长时间运行的任务需要停止时,或者当您有一个守护进程需要关闭时,中断线程非常有用。

    • 如何打断**

要想中断,你可以在线程上调用interrupt()。这是一个协作进程,所以你的代码必须做好准备,就像这样:

myThread.interrupt();
    • 负责代码**

你的代码的职责是为任何中断做好准备。我可以说,每当你有一个长时间运行的任务时,你就插入一些中断准备代码,如下所示:

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

     ... do something long

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

"* 如何阻止我的所作所为**
您有几个选项:
1.如果你在Runnable. run()中,只需返回或跳出循环并完成该方法。
1.你可能在代码深处的某个方法中,在那个时候抛出
InterruptedException
可能是有意义的,所以你可以直接这么做(保持标志清除)。
1.但是在你的代码中,抛出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.你的代码应该重置中断标志,因为如果你不这样做,你正在使用的线程可能会回到一个中断状态的线程池,从而导致问题(当然,这是线程池代码中的一个bug,例如,如果你使用Executors. newFixedThreadPool(),你就不会得到这种行为。但是其他线程代码可能会有这种行为。
1.另一个答案是,清除interrupted标志表示您已经收到消息并正在采取行动,如果您将其保留为true,则过一会儿调用者会认为您不会及时响应它。

    • 为什么interrupt()而不是代码中的其他标志?**

中断是最好的中断机制,因为我们的代码可以随时准备好。如果我们发现代码只是捕捉并忽略了InterruptExceptions,或者没有检查其主体中的interrupted(),那么我们可以纠正这些错误,使我们的代码始终可以干净地中断,而不会在代码中创建对非标准机制的神秘依赖。
不幸的是,Joshua Block在他的名著《Effective Java,Second Edition》中提出了相反的建议,但是让**interrupt()**方法按预期工作要好得多,因为这段代码是由Java标准库使用的,正如上面所解释的,而定制的中断方法则不是。

相关问题