Bug 篇:Java 8 Parallel Stream 陷阱

誤區一:Java 8 中的 Stream 只要使用 parallel 就可以並行處理,只要使用 sequential 就可以單線程處理
  • parallel 方法和 sequential 方法不會對流產生任何影響,只是改動了狀態位而已
  • Stream 是否並行取決於最後一次改變狀態位的方法是 parallel 還是 sequential,下面兩種表達式等價:
  1. stream.parallel().filter(null).sequential().map(null).parallel().collect(null);
  2. stream.parallel().filter(null).map(null).collect(null);
誤區二:只要簡單地加上 parallel 方法,就能使 Stream 高效並行
  • 並行要求計算的規模要夠大,確保在線程中運算的時間多過分配線程的開銷
  • 並行的數據必須是無序並且無狀態的,前者會影響速度,後者會影響結果正確性(狀態可以理解爲 Java 中共享變量)
/* 從一加到一千萬 */
public class ParallelStreamToGetSumTest {
    
    private long num = 10_000_000;

    /****************************** sequectial *******************************/

    @Test // 正確的順序計算
    public void sequential() {
        PerformanceUtils.test(() ->
                LongStream.rangeClosed(1, num).sum());  // 運行時間 19 milliseconds
    }

    @Test // 錯誤的順序計算,沒有考慮拆箱的開銷
    public void errorSequential() {
        PerformanceUtils.test(() ->
                Stream.iterate(1L, i -> i + 1L).limit(num)
                        .reduce(0L, Long::sum)); // 運行時間 256 milliseconds
    }

    /****************************** parallel *******************************/

    @Test // 正確的並行計算
    public void parallel() {
        PerformanceUtils.test(() ->
                LongStream.rangeClosed(1, num).parallel().sum()); // 運行時間 4 milliseconds
    }

    @Test // 錯誤的並行計算,沒有考慮開箱的開銷與違反了無序性原則(iterate 沒有範圍,是順序產生的)
    public void errorParallel() {
        PerformanceUtils.test(() ->
                Stream.iterate(1L, i -> i + 1L).limit(num)
                        .parallel().reduce(0L, Long::sum)); // 運行時間 1680 milliseconds
    }

    @Test // 錯誤的並行計算,使用了共享變量,導致了線程競爭,並且共享變量的累加不是原子性的,所以結果也是錯誤的
    public void errorParallel2() {
        PerformanceUtils.test(() -> {
            Accumulator accumulator = new Accumulator();
            Stream.iterate(1L, i -> i + 1L).limit(num).parallel().forEach(accumulator::add);
            return accumulator.total;
        }); // 運行時間 1125 milliseconds,但結果錯誤
    }

    /****************************** inner class *******************************/

    private static class Accumulator {
    
        private long total = 0;

        private void add(long value) {
            total += value;
        }
    }
}

P.S. PerformanceUnitls 是筆者寫的性能測試工具,源碼位於 https://github.com/free-myself/commons/blob/master/src/main/java/org/tree/commons/utils/PerformanceUtils.java

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章