Java同時プログラミングシリーズ---Fork/Joinフレームワーク


一、Fork/Joinフレームワークとは
Fork/Joinフレームワークは、Java 7が提供する並行してタスクを実行するためのフレームワークであり、大きなタスクをいくつかの小さなタスクに分割し、最終的に各小さなタスクの結果をまとめて大きなタスクの結果を得るフレームワークである.Fork/JoinフレームワークをForkとJoinの2つの単語で理解してみましょう.Forkは1つの大きなタスクをいくつかのサブタスクに分割して並列に実行し,Joinはこれらのサブタスクの実行結果を統合し,最後にこの大きなタスクの結果を得る.たとえば1+2+…+10000を計算すると,10個のサブタスクに分割でき,各サブタスクはそれぞれ1000個の数を合計し,最終的にこの10個のサブタスクの結果をまとめる.
二、作業窃盗アルゴリズム
ワーク・ストリング・アルゴリズムとは、あるスレッドが他のキューからタスクを盗み出して実行することを意味します.では、なぜ仕事盗みアルゴリズムを使う必要があるのでしょうか.もし私たちが比較的大きなタスクをする必要があるならば、このタスクをいくつかの互いに依存しないサブタスクに分割することができて、スレッド間の競争を減らすために、これらのサブタスクをそれぞれ異なるキューに置いて、そして各キューのために1つの個別のスレッドを作成してキュー内のタスクを実行して、スレッドとキューは1つ1つ対応します.例えばAスレッドはAキュー内のタスクの処理を担当する.ただし,あるスレッドはまず自分のキューのタスクを完了し,他のスレッドに対応するキューにはタスク待機処理がある.仕事を終えたスレッドは待つよりも、他のスレッドの仕事を手伝ったほうがいいので、他のスレッドのキューからタスクを盗んで実行します.この場合、同じキューにアクセスするため、タスクスレッドの盗難と盗まれたタスクスレッドの競合を減らすために、通常、両端キューが使用され、盗まれたタスクスレッドは両端キューのヘッダからタスクを実行し、盗まれたタスクのスレッドは両端キューの末尾からタスクを実行します.
作業窃盗アルゴリズムの利点:スレッドを十分に利用して並列計算を行い、スレッド間の競争を減らす.
ワーク・盗難アルゴリズムの欠点:デュアル・エンド・キューにタスクが1つしかない場合など、場合によっては競合があります.このアルゴリズムは、複数のスレッドの作成や複数の両端キューの作成など、より多くのシステムリソースを消費します.
三、Fork/Joinの使用
1+2+3+4の結果を計算するFormk/Joinフレームワークを簡単なニーズで使用しましょう.
Fork/Joinフレームワークを使用するには、まずタスクをどのように分割するかを考慮します.サブタスクごとに最大2つの数の加算を実行したい場合は、分割のしきい値を2に設定します.4つの数値加算なので、Fork/Joinフレームワークはこのタスクforkを2つのサブタスクにします.サブタスク1は1+2を計算し、サブタスク2は3+4を計算します.そしてjoinの2つのサブタスクの結果.結果のあるタスクであるため、RecursiveTaskを継承しなければならず、実装コードは以下の通りである.
package com.example.demo.thread;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;

/**
 * @author : pengweiwei
 * @date : 2020/2/18 8:00   
 */
public class ForkJoinDemo extends RecursiveTask<Integer> {

    //   
    private static final int THRESHOLD = 2;
    private int start;
    private int end;

    public ForkJoinDemo(int start, int end) {
        this.start = start;
        this.end = end;
    }


    @Override
    protected Integer compute() {
        int sum = 0;
        //          
        boolean flag = (end - start) <= THRESHOLD;
        if (flag) {
            for (int i = start; i <= end; i++) {
                sum += i;
            }
        } else {
            //         ,           
            int middle = (start + end) / 2;
            ForkJoinDemo leftTask = new ForkJoinDemo(start, middle);
            ForkJoinDemo rightTask = new ForkJoinDemo(middle + 1, end);
            //     
            leftTask.fork();
            rightTask.fork();
            //         ,      
            int leftResult = leftTask.join();
            int rightResult = rightTask.join();
            //      
            sum = leftResult + rightResult;
        }
        return sum;
    }

    public static void main(String[] args) {
        //         ,    1+2+3+4
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinDemo task = new ForkJoinDemo(1,4);
        //       
        Future<Integer> result = forkJoinPool.submit(task);
        try {
            System.out.println(result.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}


この例を通して,ForkJoinTask,ForkJoinTaskと一般タスクの主な違いはcomputeメソッドを実現する必要があることであり,このメソッドではまずタスクが十分に小さいかどうかを判断し,十分に小さい場合に直接タスクを実行する必要がある.十分に小さくない場合は、2つのサブタスクに分割する必要があります.各サブタスクはforkメソッドを呼び出すとcomputeメソッドに入り、現在のサブタスクがサブタスクに分割を続ける必要があるかどうかを確認し、分割を続ける必要がない場合は、現在のサブタスクを実行し、結果を返します.joinメソッドを使用すると、サブタスクの実行が完了するのを待って結果が得られます.