繁体   English   中英

以确定性方式对有序流过程元素进行有状态映射操作?

[英]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

问题:

  1. 每次运行时输出都会改变。 如何确保有状态地图操作按顺序执行。
  2. map 是一个中间操作,它只开始处理元素,直到终端操作开始。 既然终端操作是有序的,为什么 map 操作是无序的,并且在使用有状态操作时每次都会改变结果?

您很幸运地看到serialStorage拥有您认为的所有元素,毕竟您是从多个线程向非线程安全集合ArrayList添加多个元素。 您可以很容易地看到null或没有所有元素的List 但即使您添加了一个线程安全的List - 在该 List 中绝对没有可以依赖的顺序。

这在side-effects下的文档中明确提到,中间操作应该是无副作用的。

基本上有两种排序:处理顺序(中间操作)和遭遇顺序 最后一个被保留(如果它有一个开始并且流中间操作不会破坏它 - 例如unorderedsorted )。

未指定处理顺序,这意味着所有中间操作都将按照他们喜欢的顺序处理元素。 遇到顺序(您从终端操作中看到的顺序)将保留初始顺序。

但即使是终端操作也不必保留初始顺序,例如forEachforEachOrdered或当您收集到Set 当然阅读文档,它通常清楚地说明这方面。

我想回答你的 2 个问题,同时补充这个其他答案......

  1. 每次运行时输出都会改变。 如何编写代码以有序的方式处理有状态地图操作?

不鼓励有状态的映射操作,您不应该使用它们,即使对于顺序流也是如此。 如果您想要这种行为,最好使用命令式方法。

  1. map 是中间操作,它只开始处理元素,直到终端操作开始。既然终端操作是有序的,为什么 map 操作是无序的,并且在使用有状态操作时每次都会改变结果?

只有forEachOrdered尊重元素的相遇顺序 中间操作(例如map )不是被迫这样做的。 对于并行流,这意味着允许管道以任何顺序执行中间操作,从而利用并行性。

但是,请记住,提供状态参数的中间操作中(即有状态的mapper功能将map操作)时,流并行,需要您手动同步由状态参数保持状态(即你需要使用列表的同步视图,或实现一些锁定机制等),但这反过来会对性能产生负面影响,因为(如文档中所述)您可能会因争用而破坏您正在寻求从中受益的并行性.

编辑:对于像forEachOrdered这样的终端操作,并行通常不会带来什么好处,因为很多时候它需要做一些内部处理来遵守尊重相遇顺序的要求,即缓冲元素。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM