如何在java中安全地使用并行流?

时间:2017-10-27 14:05:22

标签: java parallel-processing

我在大型计算中使用并行流,但是当我执行它时结果很奇怪。在纯[SEQ] - 进程调度中执行所有操作时,它只能正常工作。

为了找到问题,我使用了一个简单的计算(工作是将“迭代”中的所有自然数加到零):

private long sum = 0;                  // global helper

public static void main(String args[])
{
    int iterations = 50000;            // the target number

    // solving the task parallel
    // ---------------------------------- <Section-under-Test>.START
    final long    startPAR =     System.nanoTime();
    IntStream.range(1, iterations+1).parallel().forEach(i->{

        sum += i;
    });
    final long durationPAR = ( ( System.nanoTime() - startPAR ) / 1_000_000 );
    // ---------------------------------- <Section-under-Test>.FINISH
    long sumParallel = sum; sum = 0;   // save + reset variable

    System.out.println( "Sum parallel: "  + sumParallel
                      + "         TOOK: " + durationPAR
                        );

    // solving the task linear using one core
    // ---------------------------------- <Section-under-Test>.START
    final long    startSEQ =     System.nanoTime();

    for(int i = 1; i < iterations+1; i++)
    {
        sum += i;
    }
    final long durationSEQ = ( ( System.nanoTime() - startSEQ ) / 1_000_000 );
    // ---------------------------------- <Section-under-Test>.FINISH

    System.out.println( "Sum serial:   "  + sum
                      + "         TOOK: " + durationSEQ
                        );
    System.out.println((sum == sumParallel));
}

奇怪的是,每次执行并行部分时都会得到不同的输出:

Sum parallel: 354519954
Sum serial:  1250025000
false
---------------------------
Sum parallel: 453345292
Sum serial:  1250025000
false
---------------------------
Sum parallel: 613823840
Sum serial:  1250025000
false

所以我想知道的是:

  • 为什么会这样?
  • 如何防止这种情况?

我是否忽略了这一点,并希望在错误的地方使用并行计算?

的信息:

在我更大的计算中,我正在计算要添加的并行值,在本例中为总和。所以这很好用。但是,如何将此结果正确添加到全局变量?

2 个答案:

答案 0 :(得分:2)

因为sum += i;不是原子的,所以它涉及多个操作:

  1. 阅读i
  2. 阅读sum
  3. 计算sumi
  4. 将结果分配给sum
  5. 在执行这些操作期间的任何时候,我们都有其他线程执行相同的操作。如果两个线程同时读取sum(操作2),则可以保证得到不同的结果,因为当它们都计算sum + i时,两个线程都不会考虑另一个线程。

    如果您想并行执行此类计算,请改用AtomicLongaddAndGet之类的操作保证是原子的,也就是说,它保证在一个步骤中发生,而不是分解为上述步骤。

答案 1 :(得分:2)

只需使用 .sum() 方法,以便没有线程在共享状态下运行,因此无需进行同步或原子访问:

int sum = IntStream.rangeClosed(1, iterations).parallel().sum();

如果您需要更多控制计算,可以使用 .reduce() 方法:

int sum = IntStream.rangeClosed(1, iterations).parallel().reduce(0, (a, b) -> a + b);