【问题标题】:Java's FutureTask composabilityJava 的 FutureTask 可组合性
【发布时间】:2013-01-20 05:15:49
【问题描述】:

我尝试使用 Java 的 FutureTask、Future、Runnable、Callable 和 ExecutorService 类型。

组成这些构建块的最佳实践是什么?

鉴于我有多个FutureTasks,并且我想按顺序执行它们。

当然,我可以创建另一个 FutureTask,它依次为每个子任务提交/等待结果,但我想避免阻塞调用。

另一种选择是让这些子任务在完成时调用回调,并在回调中安排下一个任务。但是走这条路,我如何创建一个适当的外部 FutureTask 对象,该对象还可以处理子任务中的异常而不产生那么多样板文件?

我错过了什么吗?

【问题讨论】:

    标签: java android concurrency


    【解决方案1】:

    非常重要的事情,虽然教程中通常没有描述:

    Runnables to be executed on an ExecutorService should not block。这是因为每次阻塞都会关闭一个工作线程,如果 ExecutorService 的工作线程数量有限,就有陷入死锁(线程饥饿)的风险,如果 ExecutorService 的工作线程数量不受限制,那么就有风险内存不足。任务中的阻塞操作只会破坏 ExecutorService 的所有优点,所以只能在普通线程上使用阻塞操作。

    FutureTask.get() 是阻塞操作,因此可以在普通线程上使用,而不是从 ExecutorService 任务中使用。也就是说,它不能作为构建块,而只是将执行结果传递给主线程。

    从任务构建执行的正确方法是在下一个任务的所有输入数据都准备好时启动下一个任务,这样任务就不必阻塞等待输入数据。因此,您需要一种门来存储中间结果并在所有参数到达时启动新任务。因此,任务不会显式地启动其他任务。因此,由用于参数的输入套接字和用于计算它们的 Runnable 组成的门可以被视为在 ExcutorServices 上进行计算的正确构建块。

    这种方法称为数据流或工作流(如果不能动态创建门)。

    像 Akka 这样的 Actor 框架使用这种方法,但受限于 Actor 是具有单个输入套接字的门这一事实。

    我写了一个真实的数据流库,发布在https://github.com/rfqu/df4j。

    【讨论】:

    • 感谢您的解释。它基本上很好地指出了我的问题。问题是,如果您有某种想要重用的任务,那么之后要执行的逻辑不能包含在该任务中,并且 Future 接口缺少异步回调。我最近遇到了code.google.com/p/guava-libraries/wiki/…,它也试图解决这个问题。但我通常想知道它是如何工作的,基本上,如果我不认为他们没有考虑过这个设计界面,我仍然不明白。
    • 感谢的最佳方式是为答案投票。 df4j 有几种构建块,包括CallbackPromise,它接近于番石榴的ForwardingCheckedFuture。如果您发现其他类型的构建块值得包含在库中,只需在 github.com/rfqu/df4j/issues 提交问题
    • 您似乎误会了,Akka 数据流不使用演员:doc.akka.io/docs/akka/2.1.0/scala/dataflow.html,Akka Futures(现在在 Scala 标准库中,也不使用演员:doc.akka.io/docs/akka/2.1.0/scala/futures.html)
    • @Viktor Klang Akka 是一个庞大的框架,主要围绕 Actor 概念构建。它不使用 scala 原生演员,而是大量使用自己的演员系统变体。谷歌查询 actor site:http://doc.akka.io 产生 >3000 个答案,例如http://doc.akka.io/docs/akka/2.1.0-RC2/general/actor-systems.html。 Akka dataflow 可能是一个不使用演员的小子集,但我的意思是整个 Akka。
    • 我根本看不出这些因素是如何影响的。 Akka Dataflow 不使用 Actors 并且非常轻量级,事实上,它是一个大约 100 行(主要是文档)的单个源文件,并且只需要 Scala:github.com/akka/akka/blob/master/akka-dataflow/src/main/scala/…
    【解决方案2】:

    我尝试对 ScheduledFuture 做类似的事情,试图在向用户显示内容之前造成延迟。这就是我想出的,只需对所有“延迟”使用相同的 ScheduledFuture。代码是:

        public static final ScheduledExecutorService scheduler = Executors
            .newScheduledThreadPool(1);
        public ScheduledFuture delay = null;
    
        delay = scheduler.schedule(new Runnable() {
        @Override
        public void run() {
        //do something
        }
        }, 1000, TimeUnit.MILLISECONDS);
    
        delay = scheduler.schedule(new Runnable() {
        @Override
        public void run() {
        //do something else
        }
        }, 2000, TimeUnit.MILLISECONDS);
    

    希望这会有所帮助 安迪

    【讨论】:

    • 请注意,ScheduledFuture 可以在其 Runnable 执行之前取消。在示例中,您将无法取消第一个作业(除非从调度程序的队列中获取它)。
    • 感谢指正。只是出于兴趣,如果我有两个 ScheduledFuture 的 delay1 和 delay2,上面的示例会按预期工作吗?
    • 第一次调用时,调度器会将runnable包装到一个future中,将其放入任务队列并返回给调用线程。无论您是否将其实际放入变量中,调度程序都无关紧要。所以保留未来只是为了以后取消或者用getDelay查询执行时间。
    【解决方案3】:

    通常的做法是:

    • 决定 ExecutorService(哪种类型,多少线程)。
    • 决定任务队列(非阻塞的时间长度)。

    如果您有一些外部代码等待任务结果: * 将任务作为 Callables 提交(只要您没有用完队列,这就是非阻塞的)。 * 调用 get on the Future。

    如果您希望在任务完成后自动执行某些操作:

    • 您可以作为 Callables 或 Runnables 提交。
    • 只需在最后添加您需要做的任务作为任务中的最后一个代码。采用 Activity.runOnUIThread 这些最终动作需要修改 GUI。

    通常,您不应该主动检查何时可以提交更多任务或安排回调以提交它们。线程队列(阻塞,如果愿意)将为您处理。

    【讨论】:

      猜你喜欢
      • 2012-11-30
      • 1970-01-01
      • 2013-08-20
      • 1970-01-01
      • 2011-06-24
      • 2021-10-28
      • 1970-01-01
      • 2021-07-11
      • 1970-01-01
      相关资源
      最近更新 更多