Java长时间运行任务:线程中断与取消标志

16

我有一个长时间运行的任务,类似于:

public void myCancellableTask() {
    while ( someCondition ) {
       checkIfCancelRequested();
       doSomeWork();
    }
 }

任务可以被取消(即请求取消并检查checkIfCancelRequested()的取消标志)。通常,当我编写类似的可取消循环时,我使用一个标志来指示已经请求了取消。但是,我知道我也可以使用Thread.interrupt并检查线程是否已被中断。我不确定哪种方法更好,以及原因是什么,有什么想法吗?

谢谢,

Jeff

5个回答

24

使用中断的一个问题是,如果你不能控制所有正在执行的代码,你面临着由于其他人对中断处理方式的错误理解而导致中断无法正常工作的风险,因为API在其处理interrupt时会隐式地导出一个API,你会依赖于它。

以您的示例为例,假设doSomeWork在一个第三方JAR中,并且如下所示:

public void doSomeWork() {
    try { 
        api.callAndWaitAnswer() ; 
    } 
    catch (InterruptedException e) { throw new AssertionError(); }
}

现在你需要处理一个AssertionError(或者你使用的库可能会抛出其他异常)。我曾经见过有经验的开发人员在接收到中断时抛出各种无意义的东西!另一方面,也许这个方法看起来像这样:

public void doSomeWork() {
    while (true) {
        try { 
            return api.callAndWaitAnswer() ; 
        } 
        catch (InterruptedException e) { /* retry! */ }
    }
}

这种"不当处理"中断的方式会导致程序无限循环。不要认为这是荒谬的;目前有很多破损的中断处理机制。

至少使用自己的标记对于任何第三方库都是完全不可见的。


1
这里有好东西。很常见的情况是吞噬中断或仅记录并在不重置中断标志的情况下继续执行。我认为人们会因为它是一个已检查异常而感到沮丧,他们不知道该怎么做,所以干脆什么都不做。 - Jeff Storey

9

中断会将线程从指定的等待条件列表中强制退出。但是您自己的取消标志不会。如果您想要中断IO和事件等待,请使用中断。否则请使用您自己的。


1

这取决于doSomeWork()的实现。它是纯计算还是(在任何时候)涉及阻塞API(例如IO)调用?根据bmargulies的答案,JDK中的许多阻塞API都是可中断的,并将中断异常向上传播。

因此,如果工作涉及潜在的阻塞活动,即使您决定使用标志控制进程,也需要考虑中断,并应适当捕获和处理/传播中断。

除此之外,如果依赖标志,请确保您的标志声明具有volatile语义。


1

首先,让我们看一下使用条件。

如果我们有一个线程池,并使用中止作为取消机制,那么我们只能通过池来中断工作线程。换句话说,我们不能直接调用Thread.interrupt,因为我们不拥有这些线程。因此,我们必须获取一个Future并调用Future.cancel。或者我们必须调用ExecutorService.shutdownNow来取消所有任务并中断忙碌的线程。在第一种情况下,我们需要在我们这边进行一些簿记工作以获得Future句柄。因此,应用程序必须保留新任务并删除旧任务。

另一方面,如果您使用全局取消标志, 我们可以从一个集中地方取消/停止多个任务而不需要额外的簿记工作。但是,如果我们想要取消单个任务 - 类似于调用Future.cancel - 我们必须为每个任务存储一个取消标志。

其次,让我们检查一下一般约定。

Java类库通常将线程中断解释为取消请求。例如,LinkedBlockingQueue.take 可以使我们的程序阻塞。但是当当前线程被中断时,它会抛出一个 InterruptedException 并返回。因此,我们可以使用响应式方法使我们的应用程序变得更加响应。所以我们可以在我们的代码中建立在已经存在的支持之上,并编写额外的 Thread.interrupted/Thread.currentThread().isInterrupted 检查。

此外,ExecutorService 中的取消方法使用线程中断。正如我们提到的,Future.cancelExecutorService.shutdownNow 依赖于中断。


0

我认为在大多数情况下这是个偏好问题。 个人而言,我会选择手动标记。这样可以更好地控制代码——例如,您可以确保线程不会使其他对象处于不一致的状态。 此外,如果性能真的很关键,请记住使用异常会有开销(即使在99%的情况下这种开销微不足道)。


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