繁体   English   中英

并行流() java 1.8 与 11

[英]parallelStream() java 1.8 vs 11

考虑以下代码:

public class StreamDemo {
    public static void main(String[] args) {
        StreamObject obj = new StreamObject();
        obj.setName("mystream");

        List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));

        list.parallelStream().forEach(l -> {
            obj.setId(l);
            System.out.println(obj + Thread.currentThread().getName());
        });
    }

    static public class StreamObject {
        private String name;
        private Integer id;

        // getters, setters, toString()
    }
}

当它使用 java 11 编译和运行时,它返回以下内容:

StreamObject{name='mystream', id=4}ForkJoinPool.commonPool-worker-23
StreamObject{name='mystream', id=4}main
StreamObject{name='mystream', id=4}ForkJoinPool.commonPool-worker-9
StreamObject{name='mystream', id=4}ForkJoinPool.commonPool-worker-5
StreamObject{name='mystream', id=4}ForkJoinPool.commonPool-worker-19

但是对于 java 1.8,它返回不同的结果:

StreamObject{name='mystream', id=3}main
StreamObject{name='mystream', id=5}ForkJoinPool.commonPool-worker-2
StreamObject{name='mystream', id=2}ForkJoinPool.commonPool-worker-9
StreamObject{name='mystream', id=1}ForkJoinPool.commonPool-worker-11
StreamObject{name='mystream', id=4}ForkJoinPool.commonPool-worker-4

为什么结果不一样?

这两个结果都与 Java Memory Model 一致。

执行发生的一种可能顺序是:

T1 calls setId
T1 prints
T2 calls setId
T2 prints
...
T5 calls setId
T5 prints

但是,因为您没有做任何事情来确保 set 和 print 原子发生,所以也允许以下操作(与许多其他排序一样):

T3 calls setId
T1 calls setId
T2 calls setId
T5 calls setId
T4 calls setId

T1 prints
T1 prints
...
T5 prints

因此,它们不同的原因是规范不要求它们相同。 一些微妙的(或者可能不那么微妙的)实现(或环境)差异意味着它们的执行方式不同。

但是,你说,实现上有什么区别? 这不是你应该关心的事情(这听起来像是因为不知道而大肆宣传:我真的不知道)。 您应该关心 Java Memory Model ,因为这提供了保证的属性。

例如,如果您想要“Java 8”行为,您可以在一个公共监视器上同步线程,例如obj

list.parallelStream().forEach(l -> {
    synchronized (obj) {
        obj.setId(l);
        System.out.println(obj + Thread.currentThread().getName());
    }
});

当然,线程仍然会以任意顺序执行; 但是每个线程都会打印它设置的值。

Java 8 和 Java 11 没有区别,每次运行我们得到不同的结果。 如果我们想正确打印,我们可以使用synchronize块,但在这种情况下我们将失去parallelStream的好处。

JAVA 8

在此处输入图像描述

在此处输入图像描述

JAVA 11

在此处输入图像描述

在此处输入图像描述

请注意,根据 javadoc,此行为是明确的非确定性的,因此两个输出都是有效的执行顺序。

此操作的行为是明确的非确定性的。 对于并行 stream 管道,此操作不保证遵守 stream 的遇到顺序,因为这样做会牺牲并行性的好处。 对于任何给定的元素,可以在库选择的任何时间和任何线程中执行操作。 如果操作访问共享 state,它负责提供所需的同步。

我想如果您没有使用 forEachOrdered 方法,而是在 stream 上使用 forEach,这意味着无论您使用哪个 JDK,每次都应该收到不同的值。

暂无
暂无

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

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