Collection.parallelStream()是否暗示先发生关系?

时间:2018-12-23 18:15:59

标签: java java-stream java-memory-model

考虑以下(完全人为设计的)Java代码:

final List<Integer> s = Arrays.asList(1, 2, 3);
final int[] a = new int[1];
a[0] = 100;
s.parallelStream().forEach(i -> {
    synchronized (a) {
        a[0] += i;
    }
});
System.out.println(a[0]);

此代码是否保证输出“ 106”?

除非parallelStream()建立了 happens-before 关系,否则似乎不是这样,通过该关系我们可以确定地知道对a[0]的首次访问lambda将看到100而不是零(根据我对Java内存模型的理解)。

但是Collection.parallelStream()并没有证明可以建立这种关系...

可以询问相同的问题来完成parallelStream()方法调用。

那么我是不是缺少了什么?或者为正确起见,是否需要上面的代码看起来像这样:

final List<Integer> s = Arrays.asList(1, 2, 3);
final int[] a = new int[1];
synchronized (a) {
    a[0] = 100;
}
s.parallelStream().forEach(i -> {
    synchronized (a) {
        a[0] += i;
    }
});
synchronized (a) {
    System.out.println(a[0]);
}

或者... parallelStream()是否确实提供了这些先发生关系,而这仅仅是缺少一些文档的问题?

我之所以问是因为,从API设计的角度来看,(至少对我而言)这样做似乎是合乎逻辑的……类似于Thread.start()等。

3 个答案:

答案 0 :(得分:1)

您确实应该避免在管道“外部”碰到变量。即使您使其正常工作,性能也可能会受到影响。 JDK内置了许多工具来实现此目的。例如,您的用例可能更安全,例如:

Integer reduce = IntStream.of(1, 2, 3)
            .parallel()
            .reduce(100, (accumulator, element) -> accumulator + element);

答案 1 :(得分:0)

Here是建立先发生后关系的动作的列表。 如您所见,这里没有提到parallelStream,因此请回答您的问题:不, parallelStream本身不会建立事前关联。

关于第一次访问读取为零-如果正在处理parallelStream之前主线程设置了100,则paralleStream启动的每个线程都会看到该值,引用该链接:

  

在启动线程中的任何操作之前,发生了在线程上启动的调用   线程。

顺便说一句,您的lambda表达式用法是有状态的,即discouraged

答案 2 :(得分:0)

我个人使用以下代码进行保证,

        final List<Integer> s = Arrays.asList(1, 2, 3);
        AtomicInteger atomicInteger = new AtomicInteger(100);
        s.parallelStream()
                .forEach(atomicInteger::addAndGet);
        System.out.println(atomicInteger.get());

使用并行流减少数量不是一个好习惯。