方法调用Future.get()块。这真的很可取吗?

问题描述 投票:48回答:5

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

下面是伪代码的片段。我的问题是 - 以下代码是否没有打败并行异步处理的概念?

我问这个的原因是因为在下面的代码中,主线程将提交一个要在不同线程中执行的任务。在队列中提交任务后,它会阻止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());
}
java multithreading asynchronous executorservice executor
5个回答
42
投票

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

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

你有几个选择:

  • 调用isDone(),如果结果准备就绪,请通过调用get()来询问它,注意没有阻塞
  • get()无限期地阻止
  • 使用get(long timeout, TimeUnit unit)阻止指定的超时

整个Future API的东西是从执行并行任务的线程获取值的简单方法。如果您愿意,可以同步或异步完成此操作,如上面的项目符号所述。

使用CACHE示例进行更新

这是来自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());
        }
    }
  }
}

8
投票

下面是伪代码的片段。我的问题是 - 以下代码是否没有打败并行异步处理的概念?

这一切都取决于您的用例:

  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?

1
投票

在你给你的例子中,你也可以用你的main()方法运行一切,然后开始你的快乐方式。

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

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

此外,在Java8中,有CompletableFuture支持功能样式回调。


1
投票

我想在这一点上给出我的分享,更多的是从理论的角度来看,因为已经有一些技术答案。我想根据评论作出答复:

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

这与问题中的内容一致。

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

现在,在你开始准备蛋糕之前(我们再假设,你需要事先需要所有的成分),你将不得不等待必须做最长路线的孩子。现在,你需要在开始制作蛋糕之前等待所有的成分是你的必要,而不是任务之间的依赖。你的孩子一直在尽可能地同时完成任务(例如:直到第一个孩子完成任务)。所以,总而言之,在这里你有了paralelilsm。

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


0
投票

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

用于创建新线程 - 或者通过为您的异步线程创建使用ThreadFactory或使用java.util.concurrent.Executor的某些实现来自己完成。

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

希望这可以帮助!

© www.soinside.com 2019 - 2024. All rights reserved.