Java 如何有效地使用CompletableFuture映射每个输入的异步任务

Java 如何有效地使用CompletableFuture映射每个输入的异步任务,java,multithreading,asynchronous,guava,completable-future,Java,Multithreading,Asynchronous,Guava,Completable Future,我想返回由所有键到值的映射组成的映射,即对这些键的API响应。为此,我使用了CompletableFuture和Guava。下面是我的尝试。使用Java8和线程API是否还有其他标准方法可以实现同样的效果 映射为id->apiResponse(id) 公共静态列表returnAPIResponse(整数键){ return list.newArrayList(key.toString()+“Test”); } 公共静态void main(字符串[]args){ List key=Lists.n

我想返回由所有键到值的映射组成的映射,即对这些键的API响应。为此,我使用了
CompletableFuture
Guava
。下面是我的尝试。使用Java8和线程API是否还有其他标准方法可以实现同样的效果

映射为
id->apiResponse(id)


公共静态列表returnAPIResponse(整数键){
return list.newArrayList(key.toString()+“Test”);
}
公共静态void main(字符串[]args){
List key=Lists.newArrayList(1,2,3,4);
列表未来=键
.stream()
.map(键->CompletableFuture.SupplySync(
()->新的AbstractMap.SimpleEntry(键,returnAPIResponse(键)))
.collect(Collectors.toList());
System.out.println(
futures.parallelStream()
.map(CompletableFuture::join)
.collect(Collectors.toList());
}

这里有一个有趣的行为,我将尽力解释。让我们从简单开始,让我们暂时忘记
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());
我假设您已经知道,
parallelStream
的操作是在
common
ForkJoinPool
中执行的。这个输出的含义可能也很明显:
11个线程可用,并且所有线程都被使用了

现在我将稍微修改您的示例:

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());
惊喜。创建了更多的线程,然后我们想要什么?那么,
getPoolSize
的文档说明:

返回已启动但尚未终止的工作线程数。此方法返回的结果可能与getParallelism不同,getParallelism创建线程是为了在其他线程被合作阻止时保持并行性

本例中的阻塞是通过
map(CompletableFuture::join)
实现的。您已经有效地阻止了来自
ForkJoinPool
的工作线程,它通过旋转另一个工作线程来补偿这一点


如果您不想陷入这样的意外:

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));
但不会生成补偿线程。由于
CompletableFuture.allOf
返回一个
CompletableFuture
,我需要再次流式处理以获得结果


不要让上一个流操作中的
.map(CompletableFuture::join)
愚弄你,因为上一个
CompletableFuture::allOf
已经被阻塞,等待所有任务完成。

hmm,你的问题是什么?如果您想要一个
Map
,只需替换
collect(Collectors.toMap(Map.Entry::getKey,Map.Entry::getValue))
我的问题是,如果我提供的代码是这样做的吗?最后,我希望为异步传递到映射中的每个数据键获取API响应。映射为id->apiResponse(id)。你仍然需要最终阻止某个地方以获得结果,就像你对
连接
所做的那样,你明白这一点,对吧?是的,这一部分是可以的,但是
CompletableFuture.SupplySync的实际使用和构造我不太担心。这段代码可以完成这项工作,但我想知道是否有其他更好的方法来完成这项工作。谢谢你,这是一篇非常有用的文章!对这个话题有了一些见解。
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());
parallelism : 11 current : 16
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));
CompletableFuture.allOf(list.toArray(new CompletableFuture[0])).join();