介紹
隨著多核芯片逐漸成為主流,大多數軟件開發人員不可避免地需要了解并行編程的知識。而同時,主流程序語言正在將越來越多的并行特性合并到標準庫或者語言本身之中。我們可以看到,JDK 在這方面同樣走在潮流的前方。在 JDK 標準版 5 中,由 Doug Lea 提供的并行框架成為了標準庫的一部分(JSR-166)。隨后,在 JDK 6 中,一些新的并行特性,例如并行 collection 框架,合并到了標準庫中(JSR-166x)。直到今天,盡管 Java SE 7 還沒有正式發布,一些并行相關的新特性已經出現在 JSR-166y 中:
Fork/Join 模式;
TransferQueue,它繼承自 BlockingQueue 并能在隊列滿時阻塞“生產者”;
ArrayTasks/ListTasks,用于并行執行某些數組/列表相關任務的類;
IntTasks/LongTasks/DoubleTasks,用于并行處理數字類型數組的工具類,提供了排序、查找、求和、求最小值、求最大值等功能;
其中,對 Fork/Join 模式的支持可能是對開發并行軟件來說最通用的新特性。在 JSR-166y 中,Doug Lea 實現 ArrayTasks/ListTasks/IntTasks/LongTasks/DoubleTasks 時就大量的用到了 Fork/Join 模式。讀者還需要注意一點,因為 JDK 7 還沒有正式發布,因此本文涉及到的功能和發布版本有可能不一樣。
Fork/Join 模式有自己的適用范圍。如果一個應用能被分解成多個子任務,并且組合多個子任務的結果就能夠獲得最終的答案,那么這個應用就適合用 Fork/Join 模式來解決。圖 1 給出了一個 Fork/Join 模式的示意圖,位于圖上部的 Task 依賴于位于其下的 Task 的執行,只有當所有的子任務都完成之后,調用者才能獲得 Task 0 的返回結果。
圖 1. Fork/Join 模式示意圖
可以說,Fork/Join 模式能夠解決很多種類的并行問題。通過使用 Doug Lea 提供的 Fork/Join 框架,軟件開發人員只需要關注任務的劃分和中間結果的組合就能充分利用并行平臺的優良性能。其他和并行相關的諸多難于處理的問題,例如負載平衡、同步等,都可以由框架采用統一的方式解決。這樣,我們就能夠輕松地獲得并行的好處而避免了并行編程的困難且容易出錯的缺點。
使用 Fork/Join 模式
在開始嘗試 Fork/Join 模式之前,我們需要從 Doug Lea 主持的 Concurrency JSR-166 Interest Site 上下載 JSR-166y 的源代碼,并且我們還需要安裝最新版本的 JDK 6(下載網址請參閱 參考資源)。Fork/Join 模式的使用方式非常直觀。首先,我們需要編寫一個 ForkJoinTask 來完成子任務的分割、中間結果的合并等工作。隨后,我們將這個 ForkJoinTask 交給 ForkJoinPool 來完成應用的執行。
通常我們并不直接繼承 ForkJoinTask,它包含了太多的抽象方法。針對特定的問題,我們可以選擇 ForkJoinTask 的不同子類來完成任務。RecursiveAction 是 ForkJoinTask 的一個子類,它代表了一類最簡單的 ForkJoinTask:不需要返回值,當子任務都執行完畢之后,不需要進行中間結果的組合。如果我們從 RecursiveAction 開始繼承,那么我們只需要重載 protected void compute() 方法。下面,我們來看看怎么為快速排序算法建立一個 ForkJoinTask 的子類: