1. 程式人生 > 程式設計 >JDK8並行流及序列流區別原理詳解

JDK8並行流及序列流區別原理詳解

由於處理器核心的增長及較低的硬體成本允許低成本的集群系統,致使如今並行程式設計無處不在,並行程式設計似乎是下一個大事件。

Java 8 針對這一事實提供了新的 stream API 及簡化了建立並行集合和陣列的程式碼。讓我們看一下它是怎麼工作的。

假設 myList 是 List<Integer> 型別的,其中包含 500,000 個Integer值。在Java 8 之前的時代中,對這些整數求和的方法是使用 for 迴圈完成的。

for( int i : myList){
 result += i;
}

從 Java 8 開始,我們就可以使用stream完成同樣的迴圈:

myList.stream().sum();


將此程式碼改為並行處理非常簡單,僅需要使用 parallelStream() 代替 stream() 或 parallel()搭配stream使用:

JDK8並行流及序列流區別原理詳解

mylist.stream().parallelStream().sum();

這樣就可以成功的變為並行程式,所以將一個計算擴充套件到執行緒和CPU核心上並可用很容易就可以實現。但是我們都知道,多執行緒和並行處理的開銷很大,所以重點是什麼時候使用並行流,什麼時候使用序列流才能獲得更好的效能。

首先,讓我們看看在幕後發生的事情。parallel stream 使用的是 Fork/Join 框架進行處理的,這意味著 stream 流的源會被拆分並移交給 fork/join 池中執行。

首先,我們找到了要考慮的第一點:並非所有的stream的源會像其它的stream的源一樣可拆分。例如:ArrayList的內部實現是陣列,由於可以通過計算出中間元素的索引來拆分,所以拆分這樣的源會非常容易;假如使用LinkedList,則拆分資料會複雜的多:該實現必須遍歷第一個條目中的所有元素,以便找到可以拆分的元素,所以LinkedList是並行流中效能差的例子。

JDK8並行流及序列流區別原理詳解

這是我們可以保留的關於並行流效能的第一個事實:

S : 源集合必須可以有效拆分

拆分集合、管理 Fork/Join 任務、物件建立及 GC 也是演算法上的開銷,當且僅當在CPU核心上可簡單完成或者集合足夠大時,才值得這樣做。

一個錯誤的例子:求5個整數的最大值。

Intstream.rangeClosed(1,5).reduce(Math::max).getAsInt();

系統為fork/join準備和處理資料的開銷非常大,以至於序列流在此場景中要快得多。Math.max 方法在這裡的CPU開銷並不是很高,而且資料元素很少。

舉個例子,在編寫象棋遊戲的時候,對每個棋子移動的評估。每一個評估都可以並行執行,並且我們有大量可能的下一步移動。這種情形非常適合並行處理。

這是我們可以保留的關於並行流效能的第二個事實:

N * Q: 因子”元素數量” * “ 每個元素的執行成本” 應該很大

但這同樣意味著當每個元素的操作成本更高的時候,集合可以更小。或當每個元素的操作不那麼佔用大量CPU時,我們需要一個包含許多元素的非常大的集合,以便並行流的使用的到回報。

這直接取決於我們可以保留的第三個事實

C :CPU核心數量 - 越多越好 > 必須有1個

由於管理開銷,在單核計算機上的並行流始終比序列流的效能差。

越多越好:實際上,這句話並不是在所有情況下都正確。例如:集合太小且CPU核心啟動時處於節能模式進而導致CPU無事可做。

能否使用並行流,對每個元素的功能(function)也有要求,這涉及到並行流能否按照預期工作:

要求該功能(function):

  • 獨立:每個元素的計算都不依賴或影響任何其他元素的計算
  • 無干擾:功能(function)執行的時候不會修改基礎的資料來源
  • 無狀態

例:並行流中使用有狀態lamdba方法的例項,來源自 Java JDK API

Set seen = Collection.synchronizedSet(new HashSet());
stream.parallel().map( e -> {
    if(seen.add(e))
      return 0;
    else
      return e;
  })...

於是,這是我們可以保留的第四個事實:

F :每個元素必須獨立

總結:

JDK8並行流及序列流區別原理詳解

還有其他情況不應該並行化流嗎?有。

我們要始終考慮每一個元素的功能(function)在做什麼及它是否適合執行在並行程式碼中。當方法是呼叫一些同步方法,並行流可能會在同步方法上等待,進而導致並行流的效能並沒有想象中高。

同樣的,在呼叫BI/O操作時,由於資料是按照順序讀取的,以I/O源作為流,也會發生同樣的問題。

JDK8並行流及序列流區別原理詳解

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。