[英]Fork-Join Pool in parallel streams
我在网上搜索了各种文章和 Stack Overflow 问题,但我找不到完美的答案。 有许多问题与此接近,但略有不同。
我们知道 Java 8 Streams API 在内部使用 Fork-Join Pool。
现在我的问题是如何使用 Fork-Join 池划分流管道中的任务?
假设我们有以下内容:
List myList = inputList.parallelStream().filter( x -> x>0 )
.map(x -> x+100 ).collect(Collectors.toList());
现在我们有两种使用线程池划分任务的选项。
filter
和map
作为单个任务并使用 fork-join 池运行它。filter
和map
作为两个不同的任务,并使用两个不同的 fork-join 线程池来运行它们。我也知道流是延迟传播的,所以如果我们在两者之间有一个有状态的中间操作:
List myList2 = inputList.parallelStream().filter( x -> x>0 )
.map(x -> x+5 ).sorted().map(x -> x+5 ).collect(Collectors.toList());
那么如何创建线程池呢?
PS:之前就知道map功能是可以组合的。 我只是想为这个问题举个例子。
首先,您必须使用parallel
才能使Fork-Join Pool
处于活动状态。 这个答案稍微解释了Spliterator
是如何执行拆分的; 但简单地说,拆分是使用流元素的源完成的,并且整个管道是并行处理的。 在您的示例中,它是filter
和map
(当然它也包括terminal
操作)。
对于有状态操作 - 事情更复杂。 让我们以distinct
为例,首先看看它如何处理顺序情况。
一般来说,您可能会认为使用HashSet
可以实现non-parallel
distinct
- 您是正确的。 HashSet
可以保存所有已经看到的值,并且根本不处理(发送到下一个操作)其他元素 - 从理论上讲,您可以使用非并行的distinct
操作来完成。 但是如果已知Stream
是SORTED
呢? 想想看,这意味着我们可以保留一个标记为seen
元素(与之前的HashSet
相反)。 基本上,如果你有:
1,1,2,2,3
这意味着您的有状态操作可以在单个元素之上实现 - 而不是HashSet
; 代码将类似于:
T seen = null;
....
if(seen == null || (!currentElement.equals(seen)){
seen = currentElement;
// process seen;
}
但是这种优化只有当你知道流是SORTED
时才有可能,因为这样你就知道下一个元素与你已经看到的元素相同,或者是一个新元素,这是你以前不可能看到的在其他一些先前的操作中 - 这由排序操作保证。
现在如何实现parallel distinct
。 你基本上问这个问题:
那么如何创建线程池
以同样的方式,从 Stream 的角度来看,没有任何变化, ForkJoinPool
使用相同数量的线程 - 显然,唯一改变的是流实现。
简而言之,如果您的Stream
是ORDERED
则内部实现使用LinkedHashSet
(实际上是它的多个实例,因为在这种情况下它确实减少了)来保留您的订单,如果您不关心订单,它会使用ConcurrentHashMap
- 即如果源未排序(如Set
)或者您使用了称为unordered
显式。 如果您真的想知道它是如何完成的,您也可以查找sorted
的实现。
所以底线是Fork Join Pool
不会改变基于流的实现,它使用相同的模型。 另一方面,根据您拥有的操作,Stream API 可能会使用一些有状态数据进行有状态中间操作,例如HashSet/ConcurrentHashMap
或单个元素等。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.