1. 程式人生 > >Java程式設計技術分享:Java併發之Fork-Join框架分析

Java程式設計技術分享:Java併發之Fork-Join框架分析

1、什麼是Fork/Join框架 及產生背景

Fork/Join框架是Java7提供了的一個用於並行執行任務的框架, 是一個把大任務分割成若干個小任務,最終彙總每個小任務結果後得到大任務結果的框架。上邊是書上的定義。

我們用粗話說:Fork/Join是一個框架,來解決執行效率,手段是並行,但是是拆分型的並行!則,如果一個應用能被分解成多個子任務,並且組合多個子任務的結果就能夠獲得最終的答案,那麼這個應用就適合用 Fork/Join 模式來解決。

如果有想學習java的程式設計師,可來我們的java學習扣qun:79979,2590免費送java的視訊教程噢!小編是一名5年java開發經驗的全棧工程師,整理了一份適合18年學習的java乾貨,送給每一位想學的小夥伴,歡迎大家一起學習哦。

程式設計師技術乾貨:Java併發之Fork-Join框架

產生背景:

多核處理器已廣泛應用要提高應用程式在多核處理器上的執行效率,只能想辦法提高應用程式的本身的並行能力。常規的做法就是使用多執行緒,讓更多的任務同時處理,或者讓一部分操作非同步執行,這種簡單的多執行緒處理方式在處理器核心數比較少的情況下能夠有效地利用處理資源,因為在處理器核心比較少的情況下,讓不多的幾個任務並行執行即可。但是當處理器核心數發展很大的數目,上百上千的時候,這種按任務的併發處理方法也不能充分利用處理資源,因為一般的應用程式沒有那麼多的併發處理任務(伺服器程式是個例外)。所以,只能考慮把一個任務拆分為多個單元,每個單元分別得執行最後合併每個單元的結果,而fork/join框架就是為這而生的,java7中才認識到了這個問題。

2、工作竊取(work-stealing)演算法

瞭解這個框架之前我們需要了解一下這其中最終要的演算法--工作竊取演算法。工作竊取演算法是指某個執行緒從其他佇列裡竊取任務來執行。工作竊取的執行流程圖如下

程式設計師技術乾貨:Java併發之Fork-Join框架

下邊介紹的工作竊取演算法的定義我會把我認為重要的東西標記出來,大家著重看一下:

那麼為什麼需要使用工作竊取演算法呢?假如我們需要做一個比較大的任務,我們可以把這個任務分割為若干互不依賴的子任務,為了減少執行緒間的競爭,於是把這些子任務分別放到不同的佇列裡,併為每個佇列建立一個單獨的執行緒來執行佇列裡的任務,執行緒和佇列一一對應,比如A執行緒負責處理A佇列裡的任務。但是有的執行緒會先把自己佇列裡的任務幹完,而其他執行緒對應的佇列裡還有任務等待處理。幹完活的執行緒與其等著,不如去幫其他執行緒幹活,於是它就去其他執行緒的佇列裡竊取一個任務來執行。而在這時它們會訪問同一個佇列,所以為了減少竊取任務執行緒和被竊取任務執行緒之間的競爭,通常會使用雙端佇列,被竊取任務執行緒永遠從雙端佇列的頭部拿任務執行,而竊取任務的執行緒永遠從雙端佇列的尾部拿任務執行。

工作竊取演算法的優點是充分利用執行緒進行平行計算,並減少了執行緒間的競爭,其缺點是在某些情況下還是存在競爭,比如雙端佇列裡只有一個任務時。並且消耗了更多的系統資源,比如建立多個執行緒和多個雙端佇列。

3、Fork/Join框架的設計

設計整個框架主要包括兩個步驟:

(1)分割任務

分割任務主要指的是將一個fork大類分割成小的任務,但是這些小的任務可能還是很大,所以要不停的分割,直到任務足夠小。

(2)執行任務,合併結果

剛才我們說到,不同的任務對應不通的雙端佇列,這樣的話幾個不同的佇列分別從各自的雙端佇列中執行任務,子任務執行完成之後將結果都統一的放在一個佇列中,這時候啟動一個執行緒從佇列裡邊拿資料,然後將這些資料合併。

說了這麼多定義,那麼Fork/Join到底是如何工作呢,好 下邊我們來看主要用到的類:

ForkJoinTask:我們要執行ForkJoin任務就要建立一個ForkJoinTask任務,這個任務提供fork(),join()機制,通常狀況下,我們不需要直接繼承ForkJoinTask任務,只需繼承他的子類就可以了,框架提供了下邊兩個子類:

RecursiveAction:用於沒有返回結果的任務

RecursiveTask:用於有返回結果的任務

4、Fork/Join框架的異常處理

ForkJoinTask在執行的時候可能會丟擲異常,但是我們沒辦法在主執行緒裡直接捕獲異常,所以ForkJoinTask提供了isCompletedAbnormally()方法來檢查任務是否已經丟擲異常或已經被取消了,並且可以通過ForkJoinTask的getException方法獲取異常。使用如下程式碼:

if(task.isCompletedAbnormally())

{

System.out.println(task.getException());

}

getException方法返回Throwable物件,如果任務被取消了則返回CancellationException。如果任務沒有完成或者沒有丟擲異常則返回null。

5、使用場景

工作中可能會遇到的使用場景:

(1)有大量計算工作的程式,可以將這些計算分別fork之後join

(2)處理大檔案,處理大檔案的時候我們可以將大檔案分割開來,分別處理,最後結果彙總。

(3)處理海量的資料庫的程式,因為訪問資料庫每次消耗效能最大的就是io,訪問次數多了,效能就會急劇下降,這樣的話我們分開處理,最後彙總結果,可以併發執行。

(4)高併發的訊息佇列

暫時想到這些,當然工作中遠不止這些應用場景。

(5)處理排序演算法,具體大家可以看看這個人寫的文章,很不錯,快排,歸併,和桶排序都有分析:http://blog.csdn.net/yinwenjie/article/details/72828691

6、約束條件

(1)除了fork() 和 join()方法外,執行緒不得使用其他的同步工具。執行緒最好也不要sleep()

(2)執行緒不得進行I/O操作

(3)執行緒不得丟擲checked exception

7、和普通的多執行緒相比有什麼優勢。

有人說了,想從1+2+3+。。100 這樣的程式用普通的多執行緒也可以實現,是的可以實現,但是fork/join和ThreadPoolExecutor 相比會有什麼優勢呢?

(1)程式碼量的問題,fork/join已經是整合的程式碼,我們拿來直接使用即可,不用自己在寫好多程式碼做判斷。

(2)從分治的演算法思想到fork/join框架,這種並行性的的融入可以更加高效率的解決一大批的問題。和我們一些傳統的多執行緒應用方式如ThreadPoolExecutor比起來,它有一些自己的特點。一個典型的地方就是work-stealing,它的一個優點是在傳統的執行緒池應用裡,我們分配的每個執行緒執行的任務並不能夠保證他們執行時間或者任務量是同樣的多,這樣就可能出現有的執行緒完成的早,有的完成的晚。在這裡,一個先完成的執行緒可以從其他正在執行任務的執行緒那裡拿一些任務過來執行。我們可以說這是人家學習雷鋒好榜樣。這樣發揮主觀能動性的執行緒框架肯定辦起事來就效率高了。

8、總結

fork-join 方法提供了一種表示可並行化演算法的簡單方式。所有的排序、搜尋和數字演算法都可以進行並行分解。隨著處理器數量的增長,我們將需要在程式內部使用更多的並行性,以有效利用這些處理器;我在這個fork/join框架中看到的還是分支的思想,就像對三大排序演算法,歸併排序,堆排序,快排等,都是分治的思想,希望大家可以仔細琢磨,對這個框架起碼到會用的狀態,有可能會大大提升你的工作效率。