Java 5:java.util.concurrent.FutureTask - cancel()和done()的语义

时间:2009-08-05 17:51:41

标签: java multithreading concurrency executorservice futuretask

我目前正在使用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
         ... 
      }
   }
   ...
}

当处理MyTask实例的Executor调用done()时,我会检查是否到达那里,因为任务已被取消。如果是这样,我会跳过所有剩余的活动,特别是我不会拨打sendMessage()

FutureTask.done()的文档说:

  

当此任务转换为isDone状态(无论是正常还是通过取消)时调用的受保护方法。默认实现什么都不做。子类可以重写此方法以调用完成回调或执行簿记。请注意,您可以在此方法的实现中查询状态,以确定是否已取消此任务。   (API Reference

但是我从FutureTask的文档中得不到的是语义,而 done()正在执行。如果我在开始时通过isCancelled()检查,但在其他一些线程调用我的cancel()方法之后呢?这会导致我的任务改变主意并从那时起回复isCancelled() == true吗?

如果是这样,我以后如何知道邮件是否已发送?查看isDone()只会告诉我任务的执行已经完成,但是isCancelled()也是如此,我不知道它是否能够及时发送消息。

也许这很明显,但我现在并没有真正看到它。

4 个答案:

答案 0 :(得分:4)

对于任何给定的实例,

FutureTask#done()被调用的时间不超过一次,并且只有一个原因才会调用run() - cancel()无论有没有错误,或FutureTask之前运行发生了上述事件。任何这些结果的完成记录是锁定FutureTask#done()完成的原因无法改变,无论竞争事件是否“同时发生”。

因此,在isCancelled()中,isDone()isDone()中只有一个会返回真实,然后永远更多。很难通过错误或成功完成来区分set()报告为真。您无法果断覆盖setException(Throwable)get(),因为他们都委托内部AQS来决定尝试是否记录成功产生的值或遇到异常应该坚持下去。覆盖任一方法只会让您知道它已被调用,但您无法观察基本实现所做出的决定。如果任何一个事件发生“太晚”-say,取消后,将忽略记录该值或异常的尝试。

研究实施,我看到从错误中辨别未取消的成功结果的唯一方法是咬紧牙关并致电{{1}}。

答案 1 :(得分:3)

从API(强调我的):

  

public boolean cancel(boolean mayInterruptIfRunning)

     

从界面复制的说明:Future

     

尝试取消执行此任务。 如果任务已完成,已取消或由于其他原因无法取消,则此尝试将失败。

因此,FutureTask正在假设在转换到isDone阶段时无法取消任务。

答案 2 :(得分:3)

为什么不根据 ExecutorService 返回的 Future&lt; V&gt; 对象的结果,在任务“外部”发送消息?我已经使用了这种模式,它看起来效果很好:通过 ExecutorService 提交一堆 Callable&lt; V&gt; 任务。然后,对于每个主要任务,提交等待主要任务的 Future&lt; V&gt; 的辅助任务,并仅在 Future&lt时执行一些后续操作(如发送消息) ; V&gt; 表示主要任务已成功完成。这种方法没有猜测。当对 Future&lt; V&gt; .get()的调用返回时,只要您没有调用 get <的版本,就可以保证任务已达到终端状态/ strong>接受超时参数。

如果采用这种方法,则应使用两个单独的 ExecutorService 实例:一个用于主要任务,一个用于辅助任务。这是为了防止死锁。您不希望辅助任务启动,并且可能会阻止主要任务在线程池大小受限时启动。

根本不需要扩展 FutureTask&lt; V&gt; 。只需将您的任务实施为可调用&lt; V&gt; 对象即可。但是,如果由于某种原因你想检测任务是否从 Callable&lt; V&gt; 代码中取消,只需用 Thread.interrupted()

答案 3 :(得分:-1)

我建议您编写一个小测试案例,以便在cancel()实例挂起Future时调用done(),看看会发生什么。