parallelStream() 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,returns 如下:

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,它 returns 不同的结果:

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

为什么结果不同?

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

两个结果都与 Java 内存模型一致。

一种可能的执行顺序是:

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

但是,因为您没有做任何事情来确保设置和打印以原子方式发生,所以也允许以下(与许多其他排序一样):

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

T1 prints
T1 prints
...
T5 prints

因此,它们之所以不同是因为规范不要求它们相同;一些细微的(或可能不那么微妙的)实施(或环境)差异意味着它们的执行方式不同。

但是,你说,实施有什么不同?这不是你应该关心的事情(这听起来像是在掩饰不知道:我真的不知道吨)。 您应该关心 Java 内存模型,因为它提供了有保证的属性。

例如,如果您想要 "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,此行为显然是不确定的,因此两个输出都是有效的执行顺序。

The behavior of this operation is explicitly nondeterministic. For parallel stream pipelines, this operation does not guarantee to respect the encounter order of the stream, as doing so would sacrifice the benefit of parallelism. For any given element, the action may be performed at whatever time and in whatever thread the library chooses. If the action accesses shared state, it is responsible for providing the required synchronization.