Java并行流在集合上无法正常工作
有一点背景知识,我尝试使用Java8并行流以异步方式调用多个API。我希望调用每个API,然后阻塞,直到返回所有API。我遇到了一个有趣的情况,如果我尝试流化一个映射,而不是一个列表,那么在新线程中不再调用API 如果运行以下代码,将在新线程中调用每个服务:Java并行流在集合上无法正常工作,java,multithreading,concurrency,java-stream,Java,Multithreading,Concurrency,Java Stream,有一点背景知识,我尝试使用Java8并行流以异步方式调用多个API。我希望调用每个API,然后阻塞,直到返回所有API。我遇到了一个有趣的情况,如果我尝试流化一个映射,而不是一个列表,那么在新线程中不再调用API 如果运行以下代码,将在新线程中调用每个服务: List<GitUser> result1 = Arrays.asList(service1, service2, service3).parallelStream() .map(s->s.getG
List<GitUser> result1 = Arrays.asList(service1, service2, service3).parallelStream()
.map(s->s.getGitUser())
.collect(Collectors.toList());
注意,Javadoc声明 “返回一个可能的并行流,并将此集合作为其源。此方法允许返回一个连续的流。”。流中的元素数量似乎太少,无法分解为多个线程执行的组件,因此您的代码是按顺序执行的,而不是并行执行的。仅仅因为您正在使用,并不一定意味着它将并行执行,这完全取决于库确定什么足以并行化或不足以获得最佳性能
除了Joe C在评论中提到的上述内容之外,您还需要大量增加流源代码中的元素数量,以便实际看到性能方面的任何影响。请注意,java文档声明 “返回一个可能的并行流,并将此集合作为其源。此方法允许返回一个连续的流。”。流中的元素数量似乎太少,无法分解为多个线程执行的组件,因此您的代码是按顺序执行的,而不是并行执行的。仅仅因为您正在使用,并不一定意味着它将并行执行,这完全取决于库确定什么足以并行化或不足以获得最佳性能 除了Joe C在评论中所述的上述内容之外,您还需要大量增加流源中的元素数量,以便实际看到性能方面的任何影响。如中所述,这是关于如何分配工作负载的实现细节
HashMap
有一个内部备份数组,其容量比条目(通常)高。它根据数组元素进行拆分,知道这可能会产生不平衡拆分,因为确定条目在数组中的分布方式可能代价高昂
最简单的解决方案是,当您知道只有几个元素时,减少哈希集的容量(默认容量为16):
HashMap<Integer,String> map = new HashMap<>();
map.put(0, "foo");
map.put(1, "bar");
map.put(2, "baz");
map.values().parallelStream().forEach(v -> {
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(200));
System.out.println(v+"\t"+Thread.currentThread());
});
请注意,由于舍入问题,它仍然没有为每个元素使用一个线程。如上所述,HashMap
的Spliterator
不知道元素如何分布在数组中。但它知道总共有三个元素,所以它估计在拆分后的每个工作负载中有一半的元素。三分之二被四舍五入为一,因此流
实现假设即使尝试进一步细分这些工作负载也没有好处
除了使用具有更多元素的并行流之外,没有简单的解决方法。不过,仅出于教育目的:
HashMap<Integer,String> map = new HashMap<>(4, 1f);
map.put(0, "foo");
map.put(1, "bar");
map.put(2, "baz");
map.put(3, null);
map.values().parallelStream()
.filter(Objects::nonNull)
.forEach(v -> {
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(200));
System.out.println(v+"\t"+Thread.currentThread());
});
通过插入第四个元素,我们消除了舍入问题。它还需要提供1f
的负载系数,以防止HashMap
增加容量,从而使我们回到原点(除非我们至少有八个内核)
这是一个难题,正如我们预先知道的,我们将浪费一个工作线程来检测我们的伪null
条目。但它演示了工作负载分割是如何工作的
地图中有更多的元素会自动消除这些问题
流不是为执行阻塞或休眠的任务而设计的。对于这类任务,您应该使用ExecutorService
,它还允许使用比CPU核更多的线程,这对于在整个执行时间内不使用CPU核的任务来说是合理的
ExecutorService es = Executors.newCachedThreadPool();
List<GitUser> result =
es.invokeAll(
Stream.of(service1, service2, service3)
.<Callable<GitUser>>map(s -> s::getGitUser)
.collect(Collectors.toList())
) .stream()
.map(future -> {
try { return future.get(); }
catch (InterruptedException|ExecutionException ex) {
throw new IllegalStateException(ex);
}
})
.collect(Collectors.toList());
Executors服务=Executors.newCachedThreadPool();
列表结果=
艾尔(
流(服务1、服务2、服务3)
.map(s->s::getGitUser)
.collect(收集器.toList())
).stream()
.map(未来->{
尝试{return future.get();}
捕获(InterruptedException | ExecutionException ex){
抛出新的非法状态异常(ex);
}
})
.collect(Collectors.toList());
如中所述,这是关于如何拆分工作负载的实现细节HashMap
有一个内部备份数组,其容量比条目(通常)高。它根据数组元素进行拆分,知道这可能会产生不平衡拆分,因为确定条目在数组中的分布方式可能代价高昂
最简单的解决方案是,当您知道只有几个元素时,减少哈希集的容量(默认容量为16):
HashMap<Integer,String> map = new HashMap<>();
map.put(0, "foo");
map.put(1, "bar");
map.put(2, "baz");
map.values().parallelStream().forEach(v -> {
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(200));
System.out.println(v+"\t"+Thread.currentThread());
});
请注意,由于舍入问题,它仍然没有为每个元素使用一个线程。如上所述,HashMap
的Spliterator
不知道元素如何分布在数组中。但它知道总共有三个元素,所以它估计在拆分后的每个工作负载中有一半的元素。三分之二被四舍五入为一,因此流
实现假设即使尝试进一步细分这些工作负载也没有好处
除了使用具有更多元素的并行流之外,没有简单的解决方法。不过,仅出于教育目的:
HashMap<Integer,String> map = new HashMap<>(4, 1f);
map.put(0, "foo");
map.put(1, "bar");
map.put(2, "baz");
map.put(3, null);
map.values().parallelStream()
.filter(Objects::nonNull)
.forEach(v -> {
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(200));
System.out.println(v+"\t"+Thread.currentThread());
});
通过插入第四个元素,我们消除了舍入问题。还需要提供1fExecutorService es = Executors.newCachedThreadPool();
List<GitUser> result =
es.invokeAll(
Stream.of(service1, service2, service3)
.<Callable<GitUser>>map(s -> s::getGitUser)
.collect(Collectors.toList())
) .stream()
.map(future -> {
try { return future.get(); }
catch (InterruptedException|ExecutionException ex) {
throw new IllegalStateException(ex);
}
})
.collect(Collectors.toList());