【问题标题】:Method call to Future.get() blocks. Is that really desirable?对 Future.get() 块的方法调用。这真的很可取吗?
【发布时间】:2015-09-14 12:32:30
【问题描述】:

在将其标记为重复之前,请仔细阅读问题。

下面是伪代码的sn-p。 我的问题是-下面的代码是否不会破坏并行异步处理的概念?

我问这个的原因是因为在下面的代码中,主线程将提交一个任务以在不同的线程中执行。在队列中提交任务后,它会阻塞 Future.get() 方法让任务返回值。我宁愿让任务在主线程中执行,而不是提交到不同的线程并等待结果。通过在新线程中执行任务我得到了什么?

我知道您可以等待有限的时间等,但是如果我真的关心结果怎么办?如果要执行多个任务,问题会变得更糟。在我看来,我们只是在同步进行工作。我知道提供非阻塞侦听器接口的 Guava 库。但我很想知道我对 Future.get() API 的理解是否正确。如果是正确的,为什么 Future.get() 被设计成阻塞从而破坏了并行处理的整个过程?

注意 - 为了记录,我使用 JAVA 6

public static void main(String[] args){

private ExectorService executorService = ...

Future future = executorService.submit(new Callable(){
    public Object call() throws Exception {
        System.out.println("Asynchronous Callable");
        return "Callable Result";
    }
});

System.out.println("future.get() = " + future.get());
}

【问题讨论】:

  • 这个想法是您将提交多个任务然后等待。你是对的,如果你在提交每个任务之间等待结果,那么它们将被串行处理,你不会得到任何东西。
  • @SkinnyJ 如果提交了多个任务,你怎么知道返回的结果是针对哪个任务的?我如何等待多个任务?
  • @VishalP 然后您将获得期货列表,您可以使用 isDone() 检查或使用 get() 获取结果
  • 最简单的就是使用invokeAll。否则,您可以在提交 Callable 时跟踪每个未来,并一个接一个地获取结果。无论哪种方式,处理都是并行进行的。
  • 重要的是“get”不是开始执行,“submit”是,get只是“等待”结果。所以我们可以启动多个任务(多个执行器),然后在每个任务上使用 get。这样,所有的执行器将并行运行。如果您可以在没有结果而不是 get 的情况下继续,您可以使用观察者模式

标签: java multithreading asynchronous executorservice executor


【解决方案1】:

Future 为您提供方法 isDone(),它不会阻塞,如果计算完成则返回 true,否则返回 false。

Future.get()用于检索计算结果。

你有几个选择:

  • 调用isDone(),如果结果已准备好通过调用get()请求它,请注意没有阻塞
  • 使用get() 无限期阻止
  • get(long timeout, TimeUnit unit)阻止指定超时

Future API 的全部内容是为了方便地从执行并行任务的线程中获取值。如果您愿意,这可以同步或异步完成,如上面的项目符号中所述。

使用缓存示例更新

这是 Java Concurrency In Practice 中的缓存实现,Future 的一个极好的用例。

  • 如果计算已经在运行,对计算结果感兴趣的调用者将等待计算完成
  • 如果结果在缓存中准备好,调用者将收集它
  • 如果结果尚未准备好且计算尚未开始,调用者将开始计算并将结果包装在Future 中以供其他调用者使用。

这一切都可以通过Future API 轻松实现。

package net.jcip.examples;

import java.util.concurrent.*;
/**
 * Memoizer
 * <p/>
 * Final implementation of Memoizer
 *
 * @author Brian Goetz and Tim Peierls
 */
public class Memoizer <A, V> implements Computable<A, V> {
    private final ConcurrentMap<A, Future<V>> cache
            = new ConcurrentHashMap<A, Future<V>>();
    private final Computable<A, V> c;

public Memoizer(Computable<A, V> c) {
    this.c = c;
}

public V compute(final A arg) throws InterruptedException {
    while (true) {

        Future<V> f = cache.get(arg);
        // computation not started
        if (f == null) {
            Callable<V> eval = new Callable<V>() {
                public V call() throws InterruptedException {
                    return c.compute(arg);
                }
            };

            FutureTask<V> ft = new FutureTask<V>(eval);
            f = cache.putIfAbsent(arg, ft);
            // start computation if it's not started in the meantime
            if (f == null) {
                f = ft;
                ft.run();
            }
        }

        // get result if ready, otherwise block and wait
        try {
            return f.get();
        } catch (CancellationException e) {
            cache.remove(arg, f);
        } catch (ExecutionException e) {
            throw LaunderThrowable.launderThrowable(e.getCause());
        }
    }
  }
}

【讨论】:

  • 但是你仍然在循环中等待检查​​isDone(),这不也是阻塞吗?
  • 这完全取决于您的设计。你不必。例如,您可以让一个线程在事件进入系统时调度它们,并定期检查结果。在这种情况下,如果结果未准备好检查结果,则不会阻塞,线程可以继续调度事件。这里可以使用许多模式来避免阻塞。检查可用于通知结果的观察者模式,以及活动对象模式和半同步 - 半异步,发布订阅。尝试阅读一下这些模式,以了解如何使用异步编程。
  • 你是对的。但我仍然想知道这是否变得不必要地复杂。开箱即用的侦听器解决方案会非常方便。
  • 这个监听器很可能是一个简单的观察者模式中的观察者。您可能自己在 20 分钟内就能算出一个:) 这可能会有所帮助:tutorialspoint.com/design_pattern/observer_pattern.htm
  • 肯定会获得大量有价值的信息。非常感谢:)
【解决方案2】:

下面是伪代码的sn-p。我的问题是 - 下面的代码是否不违背并行异步处理的概念?

这完全取决于您的用例:

  1. 如果你真的想阻塞直到得到结果,使用阻塞get()

  2. 如果您可以等待特定时间段而不是无限阻塞持续时间来了解状态,请使用 get() 超时

  3. 如果您可以继续而不立即分析结果并在将来检查结果,请使用CompletableFuture (java 8)

    可以显式完成的 Future(设置其值和状态),并且可以用作 CompletionStage,支持在完成时触发的相关函数和操作。

  4. 您可以从 Runnable/Callable 实现回调机制。看看下面的 SE 问题:

    Java executors: how to be notified, without blocking, when a task completes?

【讨论】:

  • 谢谢@ravnidra。那时我没有使用 1.8,所以 CompletableFuture 是毫无疑问的。但其他输入非常相关。
【解决方案3】:

我想就这一点分享我的观点,更多的是从理论上的观点,因为已经有一些技术答案。我想根据评论来回答:

让我举个例子。我提交给服务的任务结束了 提出 HTTP 请求,HTTP 请求的结果可能会占用很多 的时间。但我确实需要每个 HTTP 请求的结果。任务是 循环提交。如果我等待每个任务返回(获取),那么我 我在这里失去了并行性,不是吗?

这与问题中所说的一致。

假设你有三个孩子,你想为你的生日做一个蛋糕。因为你想制作最好的蛋糕,你需要很多不同的东西来准备它。所以你要做的就是把配料分成三个不同的清单,因为你住的地方只有 3 家超市销售不同的产品,并为你的每个孩子分配一个单一的任务,simultaneously

现在,在您开始准备蛋糕之前(让我们再次假设,您需要事先准备好所有食材)您必须等待必须走最长路线的孩子。现在,您需要在开始制作蛋糕之前等待所有成分,这是您的 必需品,而不是任务之间的依赖关系。您的孩子尽可能长时间地同时完成任务(例如:直到第一个孩子完成任务)。所以,总而言之,这里你有并行性。

当您有 1 个孩子并且您将所有三个任务分配给他/她时,描述了顺序示例。

【讨论】:

    【解决方案4】:

    在您给出的示例中,您不妨运行 main() 方法中的所有内容,然后尽情享受。

    但是让我们假设您当前正在按顺序运行三个计算步骤。为了便于理解,我们假设 step1 需要 t1 秒,step2 需要 t2 秒,step3 需要 t3 秒才能完成。所以总计算时间是t1+t2+t3。另外,让我们假设t2&gt;t1&gt;=t3

    现在让我们考虑一个场景,当我们使用Future 并行执行这三个步骤来保存每个计算结果时。您可以使用对相应期货的非阻塞 isDone() 调用来检查每个任务是否已完成。现在会发生什么?从理论上讲,您的执行速度与t2 的完成速度一样快,对吗?所以我们确实从并行性中获得了一些好处。

    另外,在 Java8 中,CompletableFuture 支持函数式回调。

    【讨论】:

    • 让我举个例子。我提交给服务的任务最终会引发 HTTP 请求,HTTP 请求的结果可能需要很长时间。但我确实需要每个 HTTP 请求的结果。任务是循环提交的。如果我等待每个任务返回(获取),那么我在这里失去了并行性,不是吗?
    • 不,只有当您的 isDone() 返回 true 时才调用 get。这比按顺序执行所有调用要好。所以本质上,你的执行速度只会与执行最慢的网络服务(包括网络等)一样快
    【解决方案5】:

    如果您不关心结果,则生成一个新线程并从该线程使用ExectorService API 提交任务。这样,您的父线程,即main 线程将不会以任何方式阻塞,它只会产生一个新线程,然后开始进一步执行,而新线程将提交您的任务。

    对于创建新线程 - 要么通过 ThreadFactory 自己创建异步线程,要么使用 java.util.concurrent.Executor 的某些实现。

    如果这是在 JEE 应用程序中并且您使用的是 Spring 框架,那么您可以使用 @async 注释轻松创建一个新的异步线程。

    希望这会有所帮助!

    【讨论】:

    • @VishalP 有答案了吗?
    猜你喜欢
    • 1970-01-01
    • 2022-07-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多