我在网上搜索了各种文章和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:我知道地图功能可以在之前结合使用。我只是想举例说明这个问题。
答案 0 :(得分:2)
首先,您必须使用parallel
来激活Fork-Join Pool
。 This answer稍微解释了Spliterator
是如何进行拆分的;但是简单来说,拆分是使用流元素的来源完成的,并且整个管道是并行处理的。在您的示例中,它是您所说的filter
和 map
(当然,它还包括terminal
操作)。
对于有状态操作-事情更加复杂。让我们以distinct
为例,首先了解一下它如何处理顺序情况。
通常,您会认为可以使用non-parallel
来实现distinct
HashSet
-这是正确的。 HashSet
可以保存已经看到的所有值,而根本不处理(发送到下一个操作)其他元素-理论上,您将使用非并行的{{1} }操作。但是,如果已知distinct
是Stream
怎么办?考虑一下,这意味着我们可以保留一个标记为SORTED
的元素(与之前的HashSet
相对)。基本上,如果您愿意:
seen
这意味着您的有状态操作可以在单个元素(而不是 1,1,2,2,3
)的顶部实现;该代码将类似于:
HashSet
但是只有当您知道流是T seen = null;
....
if(seen == null) || (!currentElement.equals(seen)){
seen = currentElement;
// process seen;
}
时,这种优化才可能实现,因为这样您就知道下一个元素与您已经看到的元素相同,或者是一个新元素,即您不可能在以前的其他一些操作中曾经见过-这是由排序操作保证的。
现在如何实现SORTED
。您基本上会问这个问题:
然后如何创建线程池
以相同的方式,从Stream的角度来看,什么都没有改变,parallel distinct
使用相同数量的线程-显然,唯一改变的是流实现。
简单来说,如果您的ForJoinPool
是Stream
,则内部实现将使用ORDERED
(实际上是多个实例,因为它确实减少了这种情况)您的订单,如果您不关心订单,它会使用LinkedHashSet
-这是源未排序(例如ConcurrentHashMap
)还是您使用了显式调用Set
。如果您确实想知道它是如何完成的,也可以查找unordered
的实现。
因此,最底层的是sorted
不会基于流更改实现,而是使用相同的模型。另一方面,根据您已有的操作,Stream API可能会将一些有状态数据用于有状态中间操作,例如Fork Join Pool
或单个元素等。