Java 7のフォーク/結合フレームワーク

しばらく前、私はJava 7のイノベーションをレビューしていましたが、とりわけ、いくつかのイノベーションをより具体的に検討することを脅かしていました。 かなりの時間が経過し、Java 7では深刻な欠陥を見つけることさえできましたが、ついに約束を果たす時がやってきました。 したがって、カットの下に、新しい実装を使用する説明と例があります ExecutorService



ExecutorService



呼び出されました ForkJoinPool



ForkJoinPool



。 この実装は、特に再帰タスクの並列化を簡素化するように設計されており、サブタスクの実行中は、サブタスクを生成したスレッドを使用できないという問題を解決します。



使用されるタイプの簡単な説明

ForkJoinTask

これは抽象クラスであり、ある意味ではストリームの軽量な類似物です。 ForkJoinPool



、以下で説明するForkJoinPool



おかげで、少数のスレッドで非常に多くのタスクを実行できることです。 これは、睡眠タスクが実際に睡眠中ではなく、他のタスクを実行している、いわゆるワークスティールによって達成されます。 このクラスには多くの興味深いメソッドがありますが、非同期タスクの起動を実行するfork()



と、タスクの完了を待機して実行結果を返すjoin()



2つだけに焦点を当てます。 すべての方法の詳細な説明は、公式ドキュメントに記載されています



RecursiveActionおよびRecursiveTask

ほとんどのタスクには、より具体的な実装が既に用意されているため、このクラス自体は実際には使用されません。 RecursiveAction



値を計算する必要はないが、何らかのアクションを実行する必要がある場合の RecursiveAction



RecursiveTask



RecursiveTask



、まだ何かを返す必要があるとき。 ご覧のとおり、これら2つのクラスは既存のものに似ています。 Runnable



Runnable



および Callable



Callable







フォークジョインプール

このクラスでは、実際のフロー間の負荷分散のトリッキーなロジックが実装されています。 原則として、外側から見ると、ハスキーの通常のスレッドプールのように見え、特別な機能は使用されていません。



フレームワークの使用例



タスクを作成します。ノードに番号が書き込まれたツリーを作成しましょう。 そのようなすべての数値の合計を計算する必要があります。 相互理解を向上させるために、ツリーノードインターフェイスを提供します。

 public interface Node { Collection<Node> getChildren(); long getValue(); }
      
      





次に、RecursiveTaskを継承して、多くのスレッドで必要な量をカウントする再帰タスクについて説明します。 computeメソッドをオーバーライドし、 fork()



およびjoin



メソッドを賢く使用するだけです。

 public class ValueSumCounter extends RecursiveTask<Long>{ private final Node node; public ValueSumCounter(Node node) { this.node = node; } @Override protected Long compute() { long sum = node.getValue(); List<ValueSumCounter> subTasks = new LinkedList<>(); for(Node child : node.getChildren()) { ValueSumCounter task = new ValueSumCounter(child); task.fork(); //   subTasks.add(task); } for(ValueSumCounter task : subTasks) { sum += task.join(); //       } return sum; } }
      
      





簡単ですね。 この幸福を別のForkJoinPool



で実行するだけForkJoinPool





 public static void main(String[] args) { Node root = getRootNode(); new ForkJoinPool().invoke(new ValueSumCounter(root)); }
      
      





そして、あなたのポケットに計算の結果ができました!



なぜこれが必要なのですか?



まず、 Future<T>



を使用するよりも、書く方がはるかに便利で直感的です。 さらに、先ほど書いたように、フォークされたタスクを実行するために専用のリアルスレッドはまったく必要ありません。 それどころか、現在参加している既存のスレッドが積極的に使用されます。 これにより、明らかに、生産性が大幅に向上します。 Oracleのエンジニアはすでにこれを行っていると確信しているため、ベンチマークの実施を開始しませんでした。



Java 7での研究の成功:)



All Articles