遇到订单友好/不友好的终端操作与并行/顺序与有序/无序流

时间:2017-07-06 16:29:06

标签: java java-8 java-stream

受到this question的启发,我开始玩有序与无序流,并行与顺序流和终端操作,这些操作尊重遭遇订单与不尊重它的终端操作。

在对链接问题的一个答案中,显示了与此类似的代码:

List<Integer> ordered = Arrays.asList(
    1, 2, 3, 4, 4, 3, 2, 1, 1, 2, 3, 4, 4, 3, 2, 1, 1, 2, 3, 4);
List<Integer> result = new CopyOnWriteArrayList<>();

ordered.parallelStream().forEach(result::add);

System.out.println(ordered);
System.out.println(result);

这些名单确实不同。 unordered列表甚至会从一次运行更改为另一次运行,表明结果实际上是非确定性的。

所以我创建了另一个例子:

CopyOnWriteArrayList<Integer> result2 = ordered.parallelStream()
        .unordered()
        .collect(Collectors.toCollection(CopyOnWriteArrayList::new));

System.out.println(ordered);
System.out.println(result2);

我希望看到类似的结果,因为流是并行和无序的(可能unordered()是冗余的,因为它已经是并行的)。但是,结果列表是有序的,即它等于源列表。

所以我的问题是为什么收集的清单是有序的? collect是否总是尊重遭遇顺序,即使对于并行,无序的流也是如此?特定的Collectors.toCollection(...)收集器是强制遭遇订单的收集器吗?

3 个答案:

答案 0 :(得分:6)

Collectors.toCollection会返回缺少Collector特征的Collector.Characteristics.UNORDERED。另一个指定Collector.Characteristics.UNORDERED的收集器可能表现不同。

那说:&#34;无序&#34;表示无保证,而保证不会发生变化。如果库发现最简单的方法是按顺序处理无序集合,则允许这样做,并允许该行为在周二更换发布版本,或者如果有满月。

(另请注意,Collectors.toCollection如果您要使用并行流,则不要求您使用并发集合实现; toCollection(ArrayList::new)可以正常工作。这是因为收集器不具有Collector.Characteristics.CONCURRENT特性,因此它使用一种集合策略,即使对于并行流也适用于非并发集合。)

如果您使用的是无序流,但收集器不是UNORDERED,反之亦然,我怀疑您是否从框架中得到任何保证。如果有一张桌子,它会说&#34;这里 DRAGONS UNDEFINED BEAVAVIOR。&#34;我还希望在这里对不同类型的链式操作有一些区别,例如: Eugene提到findFirst在这里有所不同,即使findFirst本质上是有序操作 - unordered().findFirst()也等同于findAny()

对于Stream.collect,我认为目前的实施有三种策略:

  • 顺序:启动一个累加器,将元素累积到其中(按照遭遇顺序,因为你为什么要打扰工作来洗牌?按照你得到的顺序接受它们),调用整理器。
  • 并行执行,并发收集器以及流或收集器是无序的:一个累加器,分片输入,工作线程处理每个分片中的元素,并在它们准备就绪时向累加器添加元素,调用整理器
  • 并行执行,其他任何事情:将输入分片为N个分片,每个分片按顺序累积到自己的不同累加器中,累加器与组合器功能结合,调用整理器。

答案 1 :(得分:4)

当前实现下,我检查了java-8和java-9,对于非并发收集器collect阶段忽略了无序标志(Collector.Characteristics.UNORDERED没有设置)。允许实现这样做,以某种方式类似question

在您链接的同一问题中,我确实提供了findFirst 实际如何从jdk-8更改为jdk-9的示例。

答案 2 :(得分:1)

文档Stream#collect已经提到过:

  

当并行执行时,多个中间结果可能实例化填充合并,以便保持可变数据结构的隔离。因此,即使与非线程安全的数据结构(例如ArrayList)并行执行,也不需要额外的同步来进行并行缩减。

这意味着Stream#collect做了两件事:split&amp; merge

但我在jdk-8中有一个特殊的例子,你可以获取不同的结果,:)。在Stream#generate创建无序流时,您可以在Collectors#toList上获取不同的结果,例如:

Set<Set<Integer>> result = IntStream.range(0, 10).mapToObj(__ -> {
        return unordered().parallel().collect(toSet());
}).collect(toSet());

assert result.each.size() == 100000; // ok
//                   v--- surprised, it was pass 
assert result.size() > 1; 
Stream<Integer> unordered() {
    AtomicInteger counter = new AtomicInteger();
    return Stream.generate(counter::getAndIncrement).limit(10000);
}