Java 5:java.util.concurrent.FutureTask - cancel() 和 did() 的语义
-
22-07-2019 - |
题
我目前正在使用 FutureTasks 和 Executors 在多线程环境中寻找一个令人讨厌的错误。基本思想是让固定数量的线程执行单独的 FutureTasks 来计算要显示在表中的结果(不用介意这里的 GUI 方面)。
我已经看了这么久了,我开始怀疑自己的理智了。
考虑这段代码:
public class MyTask extends FutureTask<Result> {
private String cellId;
...
protected void done() {
if (isCancelled()) return;
try {
Result r = get(); // should not wait, because we are done
... // some processing with r
sendMessage(cellId, r);
} catch (ExecutionException e) { // thrown from get
...
} catch (InterruptedException e) { // thrown from get
...
}
}
...
}
什么时候 done()
由处理 MyTask 实例的 Executor 调用,我检查是否到达那里,因为任务已取消。如果是这样,我会跳过所有剩余的活动,特别是我不会打电话 sendMessage()
.
FutureTask.done() 的文档说:
当此任务转换到状态 isDone 时(无论是正常还是通过取消),将调用受保护的方法。默认实现不执行任何操作。子类可以重写此方法来调用完成回调或执行簿记。请注意,您可以在该方法的实现中查询状态来确定该任务是否已被取消。(API参考)
但我没有从文档中得到什么 FutureTask
是语义 尽管 done()
正在被执行。如果我通过了怎么办 isCancelled()
在开始时检查,但紧接着其他线程调用我的 cancel()
方法?这会导致我的任务改变主意并回复吗 isCancelled() == true
从那时起?
如果是这样,我以后如何知道消息是否已发送?看着 isDone()
只会告诉我任务的执行已完成,但作为 isCancelled()
当时也是如此,我不知道它是否能及时发送消息。
也许这是显而易见的,但我现在还没有真正看到它。
解决方案
从API(重点煤矿):
公共布尔取消(布尔mayInterruptIfRunning)
描述从接口复制:未来
试图取消对此任务的执行。 如果任务已经完成了这一尝试失败中,已经被取消,或无法取消某些其他原因。
因此FutureTask正在关闭,当它已经转换到isDone阶段,你无法取消任务的假设。
其他提示
FutureTask#done()
对于任何给定实例,调用不会超过一次,并且仅出于一个原因调用 - run()
已完成(有或没有错误),或 cancel()
在上述任一事件发生之前运行。任何这些结果的完成记录是 闭锁. 。原因一 FutureTask
无论看似“同时”发生的竞争事件如何,已完成的事情都无法改变。
因此,在 FutureTask#done()
仅其中之一 isCancelled()
或者 isDone()
届时将返回 true,并且永远返回 true。很难区分 isDone()
通过错误或成功完成的方式报告真实情况。你不能覆盖 set()
或者 setException(Throwable)
果断地,因为两者都委托给内部 AQS
来决定是否 试图 记录成功产生值或遇到异常应该坚持下去。重写任一方法只能让您知道它被调用,但您无法观察基本实现所做的决定。如果任一事件发生“太晚”——比如说, 后 取消——记录值的尝试或异常将被忽略。
研究实现,我发现从错误中辨别出未取消的成功结果的唯一方法是硬着头皮调用 get()
.
为什么不发送消息的任务的 “外部” 的基础上,在未来
如果采取这种方法,应该使用两个单独的的ExecutorService 强>实例:一个用于主任务,一个用于次级的。这是为了防止死锁。你不想次要任务启动和潜在地从起动时的线程池的大小是有限的块主要任务。
有没有必要延伸的 FutureTask
我建议写一个小的测试案例,它允许你打电话cancel()
而你Future
比如在done()
挂起,看看会发生什么。