对 Future.get() 块的方法调用。那真的是可取的吗?

Method call to Future.get() blocks. Is that really desirable?

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

下面是伪代码的片段。 我的问题是——下面的代码不会破坏并行异步处理的概念吗?

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

我知道您可以等待有限的时间等,但如果我真的很在意结果怎么办?如果有多个任务要执行,问题会变得更糟。在我看来,我们只是在同步进行工作。我知道提供非阻塞侦听器接口的 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());
}

Future 为您提供方法 isDone(),它不阻塞,returns 如果计算完成则为真,否则为假。

Future.get()用于取回计算结果

您有两个选择:

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

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

更新缓存示例

这是 Java 并发实践 中的缓存实现,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());
        }
    }
  }
}

在您给出的示例中,您也可以 运行 您的 main() 方法中的所有内容,然后按您的意愿进行。

但是让我们假设您目前正在按顺序 运行 进行三个计算步骤。为了便于理解,我们假设步骤 1 需要 t1 秒,步骤 2 需要 t2 秒,步骤 3 需要 t3 秒才能完成。所以总计算时间是 t1+t2+t3。另外,让我们假设 t2>t1>=t3.

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

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

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

创建新线程 - 要么自己创建一个 ThreadFactory 来创建异步线程,要么使用 java.util.concurrent.Executor.

的一些实现

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

希望对您有所帮助!

Below is the snippet of the pseudo code. My question is- Does the below code not defeat the very notion of parallel asynchronous processing?

这完全取决于您的用例:

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

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

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

    A Future that may be explicitly completed (setting its value and status), and may be used as a CompletionStage, supporting dependent functions and actions that trigger upon its completion.

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

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

我想在这方面分享一下,更多的是从理论角度,因为已经有一些技术答案。我想根据评论回答我的问题:

Let me give you my example. The tasks I submit to the service end up raising HTTP requests, The result of the HTTP request can take a lot of time. But I do need the result of each HTTP request. The tasks are submitted in a loop. If I wait for each task to return (get), then I am loosing parallelism here, ain't I?

与题中所说的一致

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

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

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