我目前正在使用 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 返回对象?我用这个模式,它似乎运作良好:提交了一堆的可赎回通过的ExecutorService 任务。然后,对于每个主任务,提交等待的未来的首要任务的 并做一些后续行动次要任务(如发送的消息)仅当在未来 表示的首要任务成功完成。有这种方法没有猜测。当呼叫为未来获得()的回报,你保证任务已达到最终状态,只要你不叫的获取版本采用一个超时参数。

如果采取这种方法,应该使用两个单独的的ExecutorService 实例:一个用于主任务,一个用于次级的。这是为了防止死锁。你不想次要任务启动和潜在地从起动时的线程池的大小是有限的块主要任务。

有没有必要延伸的 FutureTask 是在所有。只要实现你的任务的可赎回 的对象。但是,如果由于某种原因,你想检测,如果任务的可赎回 代码内取消,只是检查线程的中断状态是 Thread.interrupted()

我建议写一个小的测试案例,它允许你打电话cancel()而你Future比如在done()挂起,看看会发生什么。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top