[英]does stateful map operation of ordered stream process elements in deterministic way?
我正在阅读有关 Java 流 API 的信息,并且在这里遇到了以下问题:
forEachOrdered
操作按流指定的顺序处理元素,无论流是串行执行还是并行执行。 但是,当并行执行流时,映射操作会处理由 Java 运行时和编译器指定的流元素。 因此,lambda 表达式e -> { parallelStorage.add(e); return e; }
的顺序e -> { parallelStorage.add(e); return e; }
e -> { parallelStorage.add(e); return e; }
e -> { parallelStorage.add(e); return e; }
添加元素添加到List
parallelStorage
可以改变每次代码运行时间。 对于确定性和可预测的结果,请确保流操作中的 lambda 表达式参数不是有状态的。
我测试了以下代码,实际上,它的工作原理与前面提到的一样:
public class MapOrdering {
public static void main(String[] args) {
// TODO Auto-generated method stub
List < String > serialStorage = new ArrayList < > ();
System.out.println("Serial stream:");
int j = 0;
List < String > listOfIntegers = new ArrayList();
for (int i = 0; i < 10; i++) listOfIntegers.add(String.valueOf(i));
listOfIntegers.stream().parallel().map(e - > {
serialStorage.add(e.concat(String.valueOf(j)));
return e;
}).forEachOrdered(k - > System.out.println(k));;
/*
// Don't do this! It uses a stateful lambda expression.
.map(e -> { serialStorage.add(e); return e; })*/
for (String s: serialStorage) System.out.println(s);
}
}
输出
串行流:0 1 2 3 4 5 6 7 8 9 null null 80 90 50 40 30 00
问题:
您很幸运地看到serialStorage
拥有您认为的所有元素,毕竟您是从多个线程向非线程安全集合ArrayList
添加多个元素。 您可以很容易地看到null
或没有所有元素的List
。 但即使您添加了一个线程安全的List
- 在该 List 中绝对没有可以依赖的顺序。
这在side-effects下的文档中明确提到,中间操作应该是无副作用的。
基本上有两种排序:处理顺序(中间操作)和遭遇顺序。 最后一个被保留(如果它有一个开始并且流中间操作不会破坏它 - 例如unordered
, sorted
)。
未指定处理顺序,这意味着所有中间操作都将按照他们喜欢的顺序处理元素。 遇到顺序(您从终端操作中看到的顺序)将保留初始顺序。
但即使是终端操作也不必保留初始顺序,例如forEach
与forEachOrdered
或当您收集到Set
; 当然阅读文档,它通常清楚地说明这方面。
我想回答你的 2 个问题,同时补充这个其他答案......
- 每次运行时输出都会改变。 如何编写代码以有序的方式处理有状态地图操作?
不鼓励有状态的映射操作,您不应该使用它们,即使对于顺序流也是如此。 如果您想要这种行为,最好使用命令式方法。
- map 是中间操作,它只开始处理元素,直到终端操作开始。既然终端操作是有序的,为什么 map 操作是无序的,并且在使用有状态操作时每次都会改变结果?
只有forEachOrdered
尊重元素的相遇顺序; 中间操作(例如map
)不是被迫这样做的。 对于并行流,这意味着允许管道以任何顺序执行中间操作,从而利用并行性。
但是,请记住,提供状态参数的中间操作中(即有状态的mapper
功能将map
操作)时,流并行,需要您手动同步由状态参数保持状态(即你需要使用列表的同步视图,或实现一些锁定机制等),但这反过来会对性能产生负面影响,因为(如文档中所述)您可能会因争用而破坏您正在寻求从中受益的并行性.
编辑:对于像forEachOrdered
这样的终端操作,并行通常不会带来什么好处,因为很多时候它需要做一些内部处理来遵守尊重相遇顺序的要求,即缓冲元素。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.