詳見: http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp83
這篇我們來簡要了解一下JavaSE7中提供的一個新特性 —— Fork Join 框架。
0. 處理器發展和需求背景
回想一下并發開發的初衷,其實可以說是有兩點,或者說可以從兩個方面看。
- 對于單核的處理器來說,在進行IO操作等比較費時的操作進行時,如果執行任務的方式是單任務的,那么CPU將會“空轉”,知道IO操作結束。如果有多任務的調度機制,則在一個任務不需要CPU支持的時候,CPU可以被調度處理其他任務。簡單地講,并發可以提高CPU計算資源的利用率。
- 對于多核,或者多個計算資源的情況下,并發可以在某種程度上達到“并行”,即同時運行,縮短了任務完成的時間,提高了任務完成的效率。
我們再來看一下處理器計算能力的發展(講并發或者并行基本都要提到),Intel的創始人之一Gordon Moore曾經說過一句話,大概意思是:
當價格不變時,集成電路上可容納的晶體管數目,約每隔18個月便會增加一倍,性能也將提升一倍。
我們可以這樣理解,處理器的計算能力在一定意義上和芯片上集成的晶體管數量有關,而這項繼承技術的發展史飛快的。但是,什么事情都是有一個極限的,提升計算性能僅僅靠增加晶體管數量提高處理器主頻是不現實的,于是多核處理器的概念就出來了。
隨著在硬件上多核處理器的發展和廣泛使用,軟件開發上的變革也在進行。簡單來想,對于多個不相關的小任務來講,可以分派到不同的處理器核心來進行處理。然而,對于一個比較大的任務,如何能夠充分利用多核計算資源就是一個值得考慮的問題。
解決這個問題的辦法就是“分而治之”,而Fork Join正式這樣一種思路的產物。
1. Fork Join 的設計簡介
看過《Introduction to Algorithms》(《算法導論》)的朋友們應該還記得,在講到歸并排序(Merge Sort)和快速排序的時候,有一種很簡單又很有效率的思路就是“分而治之”,即“分治法”。而Fork Join的思路也是同理,只不過劃分之后的任務更適合分派給不同的計算資源,可以并行的完成任務。

ForkJoin的任務分解和合并
當計算分別完成之后,最后再合并回來。
簡單來看,就是一個遞歸的分解和合并,知道任務小到可以接受的程度。
2. Fork Join 設計要點
Fork Join設計出來就是為了提高任務完成的效率,圍繞這個目標,有一些要點是設計中需要考慮的,下面就給出一些要點。
- 線程的管理和線程的單純性。基于如上的設計思路,我們可以看到子任務之間的相關性是相對比較簡單的,可以并行處理。為了提高效率,并不需要重量級的線程結構和對應的線程維護,線程實現簡單就好,滿足需求即可,降低維護成本。
- 隊列機制,硬件支持一定是比較有限的,那么分解的任務應該用隊列維護起來,一個好的隊列設計是很有必要的。
- “工作竊取”,也就是設計論文原文中提到的 Work Stealing 。對于負載比較輕的線程,可以幫助負載較重的執行線程分擔任務。
對于使用Fork Join的開發者來講,需要注意:
- 可用線程數和硬件支持。線程這東西,也是有開銷的東西,絕對不是越多越好,尤其在硬件基礎有限的情況下。
- 任務分解的粒度。和前者有關系,就是分解的任務,“小”到什么程度是可以接受的,不可再分。
3. Fork Join數據結構支持
按照如上設計,分解執行一個大的任務,Fork Join至少需要考慮如下一些數據結構。
- 輕量級的線程結構。
- 維護線程的線程池,負責線程的創建,數量維護和任務管理。
- 維護任務,并支持Work Stealing的雙端隊列。如下圖。

支持ForkJoin任務維護的雙端隊列Deque
對于子任務的分解,可以從后端取出分解再放入,而對于WorkStealing則可以從頭部取出,放入其他隊列的尾部。
到此,本文僅僅是對Fork Join的大致設計思路做一個描述、勾勒。下一篇文章中會對JDK1.7中給出的實現作出分析。