简体   繁体   English

如何有效地使用 CompletableFuture 到 map 每个输入的异步任务

[英]How to efficiently use CompletableFuture to map async task per input

I want to return map consisting mapping of all keys to value being API response to those keys.我想返回 map,包括所有键到值的映射,即 API 对这些键的响应。 I am using CompletableFuture and Guava for this.我为此使用CompletableFutureGuava Below is my attempt.下面是我的尝试。 Is there any other standard way to achieve the same with Java 8 and threading APIs?是否有任何其他标准方法可以通过 Java 8 和线程 API 实现相同的目标?

Map being id -> apiResponse(id) . Map 是id -> apiResponse(id)

    
    public static List<String> returnAPIResponse(Integer key) {
        return Lists.newArrayList(key.toString() + " Test");
    }

    public static void main(String[] args) {
        List<Integer> keys = Lists.newArrayList(1, 2, 3, 4);

        List<CompletableFuture<SimpleEntry<Integer, List<String>>>> futures = keys
            .stream()
            .map(key -> CompletableFuture.supplyAsync(
                () -> new AbstractMap.SimpleEntry<>(key, returnAPIResponse(key))))
            .collect(Collectors.toList());

        System.out.println(
            futures.parallelStream()
            .map(CompletableFuture::join)
            .collect(Collectors.toList()));

    }

There is an interesting behavior here that I will try my best to explain.这里有一个有趣的行为,我会尽力解释。 Let's start simple, let's forget about CompletableFuture for a second and simply do this with a plain parallelStream , with a minor debugging step added:让我们从简单的开始,让我们暂时忘记CompletableFuture并简单地使用普通的parallelStream来执行此操作,并添加了一个小的调试步骤:

List<Integer> keys = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);

Map<Integer, List<String>> result =
    keys.parallelStream()
        .map(x -> new AbstractMap.SimpleEntry<>(x, returnAPIResponse(x)))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

System.out.println("parallelism : " + pool.getParallelism() + " current : " + pool.getPoolSize());

On my machine, this prints:在我的机器上,打印:

parallelism : 11 current : 11

I assume you already know that actions of parallelStream are executes in the common ForkJoinPool .我假设您已经知道parallelStream的操作是在commonForkJoinPool中执行的。 It is probably also obvious what that output means: 11 threads were available and all of them were used. output 的含义可能也很明显: 11 threads可用并且全部使用。

I'll slightly modify your example now:我现在将稍微修改您的示例:

List<Integer> keys = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);

ForkJoinPool pool = ForkJoinPool.commonPool();
ExecutorService supplyPool = Executors.newFixedThreadPool(2);

Map<Integer, List<String>> result =
keys.parallelStream()
    .map(x -> CompletableFuture.supplyAsync(
             () -> new AbstractMap.SimpleEntry<>(x, returnAPIResponse(x)),
             supplyPool
    ))
    .map(CompletableFuture::join)
    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

 System.out.println("parallelism : " + pool.getParallelism() + " current : " + pool.getPoolSize());

It's actually just one important change, I'll let your supplyAsync run in its own thread pool;这实际上只是一个重要的变化,我会让你的supplyAsync在它自己的线程池中运行; the rest is the same. rest 是一样的。 Running this, reveals:运行它,显示:

parallelism : 11 current : 16

Surprise.惊喜。 More threads were created then what we wanted?创建了更多线程然后我们想要什么? Well, the documentation of getPoolSize says that:好吧, getPoolSize的文档说:

Returns the number of worker threads that have started but not yet terminated.返回已启动但尚未终止的工作线程数。 The result returned by this method may differ from getParallelism when threads are created to maintain parallelism when others are cooperatively blocked .此方法返回的结果可能与 getParallelism 在创建线程以在其他线程被协作阻塞时保持并行性时有所不同。

The blocking in your case happens via map(CompletableFuture::join) .您的情况下的阻塞是通过map(CompletableFuture::join)发生的。 You have effectively blocked a worker thread from ForkJoinPool and it compensates that by spinning another one.您已经有效地阻止了来自ForkJoinPool的工作线程,它通过旋转另一个工作线程来弥补这一点。


If you do not want to get into such a surprise:如果您不想遇到这样的意外:

List<CompletableFuture<AbstractMap.SimpleEntry<Integer, List<String>>>> list =
keys.stream()
    .map(x -> CompletableFuture.supplyAsync(
         () -> new AbstractMap.SimpleEntry<>(x, returnAPIResponse(x)),
         supplyPool
     ))
    .collect(Collectors.toList());

CompletableFuture.allOf(list.toArray(new CompletableFuture[0])).join();

Map<Integer, List<String>> result =
list.stream()
    .map(CompletableFuture::join)
    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

Because there is no join on the worker threads of ForJoinPool , you can drop parallelStream .因为ForJoinPool的工作线程上没有join ,所以您可以删除parallelStream Then I still block to get the result via:然后我仍然阻止通过以下方式获得结果:

CompletableFuture.allOf(list.toArray(new CompletableFuture[0])).join();

but there will be no compensating threads generated.但不会产生补偿线程。 And because CompletableFuture.allOf returns a CompletableFuture<Void> , I need to stream again to get the results.而且因为CompletableFuture.allOf返回CompletableFuture<Void> ,我需要再次 stream 来获得结果。

Don't let that .map(CompletableFuture::join) in the last stream operation fool you, there is no blocking because of the previous CompletableFuture::allOf , which already blocked and waited for all tasks to finish.不要让最后一个 stream 操作中的.map(CompletableFuture::join)欺骗你,因为之前的CompletableFuture::allOf已经阻塞并等待所有任务完成,所以没有阻塞。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何在Java 8中使用CompletableFuture启动异步任务并使主线程完成并退出 - How to use CompletableFuture in java 8 to start an async task and let main thread finish and exit 我应该如何在 springboot async 中使用 CompletableFuture 作为 void 方法 - How should I use CompletableFuture for void method in springboot async 何时使用CompletableFuture的非异步方法? - When to use non-async methods of CompletableFuture? 如何使用 CompletableFuture 将第一个 Callable 任务的结果用作所有后续 Callable 任务的 arg? - How to use CompletableFuture to use result of first Callable task as arg to all subsequent Callable tasks? 在配置tomcat只使用1个线程时,使用completablefuture进行异步处理时,它使用另一个线程如何? - While configuring tomcat to use only 1 thread, when using completablefuture for async processing, it is using another thread how? 如何强制超时并取消异步 CompletableFuture 作业 - How to enforce timeout and cancel async CompletableFuture Jobs 如何在testNG类中使用completableFuture - How to use completableFuture in testNG class 如何同时使用completablefuture和Streams - How to use completablefuture and Streams together 如何使用 join 完成 CompletableFuture - How to use join for complete CompletableFuture 如何使用CompletableFuture.thenComposeAsync()? - How to use CompletableFuture.thenComposeAsync()?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM