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での研究の成功:)