我有一个长期运行的任务,如:
public void myCancellableTask() {
while ( someCondition ) {
checkIfCancelRequested();
doSomeWork();
}
}
可以取消任务(请求取消并且checkIfCancelRequested()检查取消标志)。通常,当我编写这样的可取消循环时,我使用一个标志来指示已请求取消。但是,我知道我也可以使用Thread.interrupt并检查线程是否被中断。我不确定哪种方法是首选,为什么,想法?
感谢,
杰夫
答案 0 :(得分:20)
使用中断的一个问题是如果你不控制所有正在执行的代码,你就会冒“中断”不正常工作的风险因为别人对如何理解在他们的库中处理中断。这就是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 :(得分:6)
中断会将线程从指定的等待条件列表中弹出。你自己的取消标志不会。如果要中断IO和事件的等待,请使用中断。否则请使用自己的。
答案 2 :(得分:1)
这取决于doSomeWork()
实施。这是纯粹的计算还是(在任何时候)涉及阻止API(例如IO)调用?根据 bmargulies的答案,JDK中的许多阻塞API都是可中断的,并且会将中断的异常传播到堆栈中。
因此,如果工作需要进行潜在的阻塞活动,那么即使决定使用标志来控制进程,也需要考虑中断,并且应该适当地捕获和处理/传播中断。
除此之外,如果依赖于标志,请确保使用volatile
语义声明您的标志。
答案 3 :(得分:0)
我认为在大多数情况下这是一个偏好问题。 我个人会去手工制作的旗帜。它为您提供了更多控制 - 例如,这样您就可以确保线程不会使其他对象处于不一致状态。 此外,如果性能非常重要,请记住使用异常会产生开销(即使在99%的情况下可以忽略不计)。
答案 4 :(得分:0)
首先,让我们看一下使用条件。
如果我们有一个线程池并使用interruption as the cancellation mechanism,则只能通过该线程中断工作线程。换句话说,由于我们不拥有线程,因此无法直接调用Thread.interrupt
。因此,我们必须获取一个Future
并调用Future.cancel
。或者,我们必须调用ExecutorService.shutdownNow
来取消所有中断繁忙线程的任务。在第一种情况下,需要握有Future
手柄的一边进行簿记。因此,应用程序必须保留新任务并删除旧任务。
另一方面,如果您使用global cancellation flag,我们可以从中心位置取消/停止多个任务,而无需进行额外的簿记。但是,如果要取消单个任务(类似于调用Future.cancel
),则必须为每个任务存储一个取消标志。
其次,让我们研究一下通用约定。
Java类库通常将线程中断解释为取消请求。例如,LinkedBlockingQueue.take
可以使我们的程序块。但是,当当前线程中断时,它将抛出InterruptedException
并返回。因此,我们的应用程序通过使用响应方法变得响应。因此,我们可以在现有支持的基础上,在我们的代码中编写其他Thread.interrupted/Thread.currentThread().isInterrupted
检查。
此外,ExecutorService
中的取消方法使用线程中断。如前所述,Future.cancel
和ExecutorService.shutdownNow
依赖于中断。