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クラスを参照してください。この完全版には、変換元の画像と変換先の画像をウィンドウで表示するその他のコードも含まれます。
