繁体   English   中英

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

[英]Java 5: java.util.concurrent.FutureTask - Semantics of cancel() and done()

我目前正在使用FutureTasks和Executors在多线程环境中寻找一个令人讨厌的错误。 基本思想是让固定数量的线程执行单独的FutureTasks,计算要在aa表中显示的结果(更别关注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参考

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

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

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

对于任何给定的实例, FutureTask#done()被调用不超过一次,并且它只被调用一个原因 - run()在有或没有错误的情况下完成,或者在前面的任何一个事件发生之前运行cancel() 任何这些结果的完成记录都是锁定的 FutureTask完成的原因不会改变,无论竞争事件似乎“同时发生”。

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

研究实现,我看到从错误中识别未取消的成功结果的唯一方法是咬住子弹并调用get()

从API(强调我的):

public boolean cancel(boolean mayInterruptIfRunning)

从界面复制的描述:Future

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

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

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

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

根本没有必要扩展FutureTask <V> 只需将您的任务实现为Callable <V>对象即可。 但是如果由于某种原因你想检测任务是否从Callable <V>代码中取消,只需用Thread.interrupted()检查线程的中断状态。

我建议编写一个小的测试用例,它允许你在Future实例挂起时调用cancel() done() ,看看会发生什么。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM