Java SE 7リリースに新しく追加されたフォーク/ジョイン・フレームワークは、ExecutorServiceインタフェースの実装です。このインタフェースを使用すると、複数のプロセッサを活用できます。 このフレームワークは、再帰的に小さな単位に分割できる作業向けに設計されています。 利用可能なすべての処理能力を使用して、アプリケーションで最高の速度を達成することを目標としています。
他のExecutorServiceと同様に、フォーク/ジョイン・フレームワークではスレッド・プール内のワーカー・スレッドにタスクを分散します。 フォーク/ジョイン・フレームワークは、ワークスティーリング・アルゴリズムを使用する点で特徴的です。 仕事がなくなったワーカー・スレッドは、ビジー状態の他のスレッドからタスクをスティールする(盗む)ことができます。
フォーク/ジョイン・フレームワークの中心となるのは、AbstractExecutorServiceの拡張クラスである、ForkJoinPoolクラスです。 ForkJoinPoolは、中核的なワークスティーリング・アルゴリズムを実装しており、ForkJoinTaskを実行できます。
フォーク/ジョイン・フレームワークの使用法はシンプルです。 最初のステップとして、作業の分割を実行するコードを記述します。 コードは次のようになります。
if (my portion of the work is small enough) do the work directly else split my work into two pieces invoke the two pieces and wait for the results
このコードをForkJoinTaskのサブクラスに含めます。このクラスは通常、より特化したRecursiveTask型(結果を返すことが可能)またはRecursiveAction型のクラスとなります。
ForkJoinTask型のクラスの記述が完了したら、実行すべき作業全体を表すこのオブジェクトを作成し、それをForkJoinPoolインスタンスのinvoke()メソッドに渡します。
フォーク/ジョイン・フレームワークの動作を理解するため、シンプルな例を考えてみましょう。 画像にシンプルなぼかしを実行することにします。 変換元(source)画像は、整数の配列によって表されます。各整数には、1ピクセルのカラー値が含まれます。 ぼかしを入れた変換先(destination)画像も、変換前と同じサイズの整数の配列によって表されます。
ぼかしは、変換元の配列全体に対して、一度に1ピクセル分の処理を行うことで実行します。 各ピクセルは、その周囲のピクセルによって平均化され(赤、緑、青の各コンポーネントが平均化されます)、結果が変換先の配列に格納されます。 この場合の実装方法として、次のようなものが考えられます。
public class ForkBlur extends RecursiveAction { private int[] mSource; private int mStart; private int mLength; private int[] mDestination; private int mBlurWidth = 15; // Processing window size, should be odd. 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++) { // Calculate average. 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; } // Re-assemble destination pixel. int dpixel = (0xff000000 ) | (((int)rt) << 16) | (((int)gt) << 8) | (((int)bt) << 0); mDestination[index] = dpixel; } } . . .
次に、compute()抽象化メソッドを実装します。このメソッドは、直接ぼかしを実行するか、またはその処理を2つの小さなタスクに分割します。 配列の長さのシンプルなしきい値を使用して、ぼかしを実行するか、分割するかを判断します。
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で実行するよう設定するのは簡単です。
実行すべき作業全体を表すタスクを作成します。
// source image pixels are in src // destination image pixels are in dst ForkBlur fb = new ForkBlur(src, 0, src.length, dst);
タスクを実行するForkJoinPoolを作成します。
ForkJoinPool pool = new ForkJoinPool();
タスクを実行します。
pool.invoke(fb);
ソース・コードの完全版については、ForkBlurクラスを参照してください。この完全版には、変換元の画像と変換先の画像をウィンドウで表示するその他のコードも含まれます。