流中的分叉池

时间:2018-10-21 12:55:58

标签: java multithreading java-8 java-stream

我在网上搜索了各种文章和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());

现在,我们有两个选项可以使用线程池划分任务。

  1. filtermap作为单个任务,并使用fork-join池运行它。
  2. filtermap视为两个不同的任务,并使用两个不同的fork-join线程池来运行它们。

我也知道流是延迟传播的,所以如果我们之间有一个有状态的中间操作,则为:

List myList2 = inputList.parallelStream().filter( x -> x>0 )
    .map(x -> x+5 ).sorted().map(x -> x+5 ).collect(Collectors.toList());

然后将如何创建线程池?

PS:我知道地图功能可以在之前结合使用。我只是想举例说明这个问题。

1 个答案:

答案 0 :(得分:2)

首先,您必须使用parallel来激活Fork-Join PoolThis answer稍微解释了Spliterator是如何进行拆分的;但是简单来说,拆分是使用流元素的来源完成的,并且整个管道是并行处理的。在您的示例中,它是您所说的filter map(当然,它还包括terminal操作)。

对于有状态操作-事情更加复杂。让我们以distinct为例,首先了解一下它如何处理顺序情况。

通常,您会认为可以使用non-parallel来实现distinct HashSet-这是正确的。 HashSet可以保存已经看到的所有值,而根本不处理(发送到下一个操作)其他元素-理论上,您将使用非并行的{{1} }操作。但是,如果已知distinctStream怎么办?考虑一下,这意味着我们可以保留一个标记为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使用相同数量的线程-显然,唯一改变的是流实现。

简单来说,如果您的ForJoinPoolStream,则内部实现将使用ORDERED(实际上是多个实例,因为它确实减少了这种情况)您的订单,如果您不关心订单,它会使用LinkedHashSet -这是源未排序(例如ConcurrentHashMap)还是您使用了显式调用Set。如果您确实想知道它是如何完成的,也可以查找unordered的实现。


因此,最底层的是sorted不会基于流更改实现,而是使用相同的模型。另一方面,根据您已有的操作,Stream API可能会将一些有状态数据用于有状态中间操作,例如Fork Join Pool或单个元素等。