Java8 如何正確高效的使用並行流

正確使用並行流,避免共享可變狀態

錯用並行流而產生錯誤的首要原因,就是使用的算法改變瞭某些共享狀態。下面是另一種實現對前n個自然數求和的方法,但這會改變一個共享累加器:

public static long sideEffectSum(long n) {
	Accumulator accumulator = new Accumulator();
	LongStream.rangeClosed(1, n).forEach(accumulator::add);
	return accumulator.total;
}
public class Accumulator {
	public long total = 0;
	public void add(long value) { total += value; }
}

有什麼問題呢?

它在本質上就是順序的。每次訪問 total 都會出現數據競爭。如果用同步來修復,那就完全失去並行的意義瞭。

為瞭說明這一點,讓我們試著把 Stream 變成並行的:

public static long sideEffectParallelSum(long n) {
	Accumulator accumulator = new Accumulator();
	LongStream.rangeClosed(1, n).parallel().forEach(accumulator::add);
	return accumulator.total;
}

測試下,輸出

在這裡插入圖片描述

在這裡插入圖片描述

性能無關緊要瞭,唯一要緊的是每次執行都會返回不同的結果,都離正確值差很遠。這是由於多個線程在同時訪問累加器,執行 total += value ,而這卻不是一個原子操作。問題的根源在於, forEach 中調用的方法有副作用它會改變多個線程共享的對象的可變狀態。

要是你想用並行 Stream 又不想引發類似的意外,就必須避免這種情況。

所以共享可變狀態會影響並行流以及並行計算,要避免共享可變狀態,確保並行 Stream 得到正確的結果。

高效使用並行流

是否有必要使用並行流?

  • 如果有疑問,多次測試結果。把順序流轉成並行流輕而易舉,但卻不一定是好事
  • 留意裝箱。自動裝箱和拆箱操作會大大降低性能

Java 8中有原始類型流( IntStream 、LongStream 、 DoubleStream )來避免這種操作,但?有可能都應該用這些流。

  • 有些操作本身在並行流上的性能就比順序流差。特別是 limit 和 findFirst 等依賴於元素順序的操作,它們在並行流上執行的代價非常大。

例如, findAny 會比 findFirst 性能好,因為它不一定要按順序來執行。可以調用 unordered 方法來把有序流變成無序流。那麼,如果你需要流中的n個元素而不是專門要前n個的話,對無序並行流調用limit 可能會比單個有序流(比如數據源是一個 List )更高效。

  • 還要考慮流的操作流水線的總計算成本。

設N是要處理的元素的總數,Q是一個元素通過流水線的大致處理成本,則N*Q就是這個對成本的一個粗略的定性估計。Q值較高就意味著使用並行流時性能好的可能性比較大。

  • 對於較小的數據量,選擇並行流幾乎從來都不是一個好的決定。並行處理少數幾個元素的好處還?不上並行化造成的額外開銷
  • 要考慮流背後的數據結構是否易於分解。

例如, ArrayList 的拆分效率比 LinkedList高得多,因為前者用不著遍歷就可以平均拆分,而後者則必須遍歷。另外,用 range 工廠方法創建的原始類型流也可以快速分解。

  • 流自身的特點,以及流水線中的中間操作修改流的方式,都可能會改變分解過程的性能。

例如,一個 SIZED 流可以分成大小相等的兩部分,這樣每個部分都可以比較高效地並行處理,但篩選操作可能丟棄的元素個數卻無法預測,導致流本身的大小未知。

  • 還要考慮終端操作中合並步驟的代價是大是小(例如 Collector 中的 combiner 方法)

如果這一步代價很大,那麼組合每個子流產生的部分結果所付出的代價就可能會超出通過並行流得到的性能提升。

流的數據源和可分解性

在這裡插入圖片描述

最後, 並行流背後使用的基礎架構是Java 7中引入的分支/合並框架瞭解它的內部原理至關重要。

java 並行計算的幾點實踐總結

稍微接觸瞭 java 的並行計算,談談幾點淺顯的總結吧

並行計算不一定比串行計算快,一般在大規模問題才會顯示出優勢

結合 lambda 表達式的 parallelStream 可以方便調用並行計算,但可能會出現空指針錯誤,解決這一問題可能需要更高級的多線程知識

看網上資料,Collection 類型對並行計算支持的好,一般數組類型支持的一般。

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: