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

时间:2015-06-27 18:27:03

标签: java multithreading asynchronous executorservice executor

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

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

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

5 个答案:

答案 0 :(得分:41)

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());
        }
    }
  }
}

答案 1 :(得分:8)

  

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

这完全取决于您的使用案例:

  1. 如果您确实要阻止直到获得结果,请使用屏蔽get()
  2. 如果您可以等待特定时间段来了解状态而不是无限阻止持续时间,请使用get()超时
  3. 如果您可以继续而不立即分析结果并在将来检查结果,请使用CompletableFuture(java 8)

      

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

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

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

答案 2 :(得分:1)

在您给出的示例中,您可以使用main()方法运行所有内容,然后以快乐的方式运行。

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

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

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

答案 3 :(得分:1)

我想在这个问题上给出我的分享,更多的是理论观点,因为已经有一些技术答案。我想根据评论作出答案:

  

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

与该问题中的内容一致。

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

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

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

答案 4 :(得分:0)

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

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

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

希望这有帮助!