Fork/Join架構是ExecutorService介面的一個實現,通過它我們可以實現多進程。Fork/Join可以用來將一個大任務遞迴的拆分為多個小任務,目標是充分利用所有的資源儘可能增強應用的效能。
和任何ExecutorService介面的實現一樣,Fork/Join也會使用線程池來分布式的管理背景工作執行緒。Fork/Join架構的獨特之處在於它使用了work-stealing(工作竊取)演算法。通過這個演算法,背景工作執行緒在無事可做時可以竊取其它正在繁忙的線程的任務來執行。
Fork/Join架構的核心是ForkJoinPool類,一個AbstractExecutorService類的子類。ForkJoinPool實現了核心的work-stealing演算法並可以執行ForkJoinTask處理。
基礎用法
使用Fork/Join架構的第一步是編寫執行片段任務的代碼。要編寫的代碼類似如下虛擬碼:
if 任務足夠小: 直接執行任務else: 將任務切成兩個小任務 執行兩個小任務並等待結果
使用ForkJoinTask子類來封裝如上的代碼,通常會使用一些JDK提供的類,使用的有RecursiveTask(這個類會返回一個結果)和RecursiveAction兩個類。
在準備好ForkJoinTask子類後,建立一個代表所有任務的對象,並將之傳遞給一個ForkJoinPool執行個體的invoke()方法。
由模糊到清晰
為了輔助理解Fork/Join架構是如何工作的,我們使用一個案例來進行說明:比如對一張圖片進行模糊處理。我們用一個整型數組表示圖片,其中的每個數值代表一個像素的顏色。被模糊的圖片也用一個同等長度的數組來表示。
執行模糊是通過對代表圖片的每個像素進行處理實現的。計算每個像素與其周圍像素的均值(紅黃藍三原色的均值),計算產生的結果數組就是模糊後的圖片。由於代表映像的通常都是一個大數組,整個處理過程需要通常會需要很多時間。可以使用Fork/Join架構利用多處理器系統上的並發處理優勢來進行提速。下面是一個可能的實現:
package com.zhyea.robin; import java.util.concurrent.RecursiveAction; public class ForkBlur extends RecursiveAction { private int[] mSource; private int mStart; private int mLength; private int[] mDestination; // 處理視窗大小; 需要是一個奇數. private int mBlurWidth = 15; public ForkBlur(int[] src, int start, int length, int[] dst) { mSource = src; mStart = start; mLength = length; mDestination = dst; } protected void computeDirectly() { int sidePixels = (mBlurWidth - 1) / 2; for (int index = mStart; index < mStart + mLength; index++) { // 計算平均值. float rt = 0, gt = 0, bt = 0; for (int mi = -sidePixels; mi <= sidePixels; mi++) { int mindex = Math.min(Math.max(mi + index, 0), mSource.length - 1); int pixel = mSource[mindex]; rt += (float) ((pixel & 0x00ff0000) >> 16) / mBlurWidth; gt += (float) ((pixel & 0x0000ff00) >> 8) / mBlurWidth; bt += (float) ((pixel & 0x000000ff) >> 0) / mBlurWidth; } // 重組目標像素. int dpixel = (0xff000000) | (((int) rt) << 16) | (((int) gt) << 8) | (((int) bt) << 0); mDestination[index] = dpixel; } } ....}
現在實現抽象方法compute(),在這個方法中既實現了模糊操作,也實現了將一個任務拆分成兩個小任務。這裡僅是簡單依據數組長度來決定是直接執行任務還是將之拆分成兩個小任務:
protected static int sThreshold = 100000; protected void compute() { if (mLength < sThreshold) { computeDirectly(); return; } int split = mLength / 2; invokeAll(new ForkBlur(mSource, mStart, split, mDestination), new ForkBlur(mSource, mStart + split, mLength - split, mDestination)); }
因為上面這些方法的實現是定義在RecursiveAction的一個子類中,可以直接在一個ForkJoinPool中建立並運行任務。具體步驟如下:
1. 建立一個代表要執行的任務的對象:
// src 表示源圖片像素的數組// dst 表示產生的圖片的像素ForkBlur fb = new ForkBlur(src, 0, src.length, dst);
2. 建立一個運行任務的ForkJoinPool執行個體:
ForkJoinPool pool = new ForkJoinPool();
3. 運行任務:
pool.invoke(fb);
在原始碼中還包含了一些建立靶心圖表片的代碼。具體參考ForkBlur樣本。
標準實現
要使用Fork/Join架構按自訂的演算法在多核系統上執行並發任務當然需要實現自訂的類了(比如之前我們實現的ForkBlur類)。除此之外,在JavaSE中已經在廣泛使用Fork/Join架構的一些特性了。比如Java8中的java.util.Arrays類的parallelSort()方法就使用了Fork/Join架構。具體可以參考Java API文檔。
Fork/Join架構的另一個實現在java.util.streams包下,這也是java8的Lambda特性的一部分。