ForkJoinPool 分支合并框架

一、ForkJoinPool 分支合并框架

为了充分利用多个 CPU、多核CPU的性能优势,可以将一个任务拆分成多个小任务,把每个小任务放到多个处理器上并行执行

Fork/Join 框架: 在必要的情况下,将一个大任务,进行拆分(fork) 成若干个小任务(拆到不可再拆),再将一个一个的小任务运算的结果进行join汇总。

ForkJoinPool 分支合并框架_第1张图片


1.1 API 描述

ForkJoinPoolExecutorService的实现类,是一种特殊的线程池。

API 描述
ForkJoinPool(int parallelism) 创建一个包含 parallelism个并行线程 的 ForkJoinPool
ForkJoinPoll() Runtime.getRuntime().availableProcessors()方法的返回值作为Parallelism参数创建ForkJoinPool()

java 8 进一步扩展了ForkJoinPool的功能,为ForkJoinPool增加了通用池功能。

API 描述
ForkJoinPool commonPool() 返回一个通用池,通用池的运行状态不会受shutdown() 或者 shutdownNow()方法的影响。 如果直接使用 System.exit(0),则会受影响
int getCommonPoolParallelism() 该方法返回通用池的并行级别

创建了 ForkJoinPool实例,就可以调用 ForkJoinPoolsubmit(ForkJoinTask task) 或者 invoke(ForkJoinTask task) 方法来执行指定的任务。 其中 ForkJoinTask代表一个可以并行、合并的任务ForkJoinTask是一个抽象类,它有两个抽象子类:RecursiveAction(没有返回值) 和 RecursiveTask(有返回值)。


ForkJoinPool 分支合并框架_第2张图片


案例一

打印0-299,将大任务拆分成小任务,并交给 ForkJoinPoll 去执行。

使用 RecursiveAction 类,没有返回值。

package test;

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.TimeUnit;

class PrintTask extends RecursiveAction {

    private static final int LENGTH = 50;
    private int start;
    private int end;

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

    @Override
    protected void compute() {
        if ((end - start) < LENGTH) {
            for (int i = start; i < end; i++) {
                System.out.println(Thread.currentThread().getName() + " ---" + i);
            }
        } else {
            int middle = (start + end) / 2;
            PrintTask low = new PrintTask(start, middle);
            PrintTask high = new PrintTask(middle, end);
            //分两个任务。
            low.fork();
            high.fork();
        }
    }
}

public class TestForkJoinPool {
    public static void main(String[] args) throws InterruptedException {

        ForkJoinPool forkJoinPool = new ForkJoinPool();
        PrintTask task = new PrintTask(0,300); //打印 0-299,并不是连续的。
        forkJoinPool.submit(task);

        System.out.println( "线程数:" + forkJoinPool.getActiveThreadCount()); //1
        System.out.println("并行数量:" + forkJoinPool.getParallelism());//4 并行数量
        System.out.println("可以使用的处理器" + Runtime.getRuntime().availableProcessors());//4

        forkJoinPool.awaitTermination(2, TimeUnit.SECONDS);
        forkJoinPool.shutdown();
    }
}

案例二

将任务进行分解,并调用分解后的任务的fork()方法使它们并行执行。RecursiveTask 具有返回值。

package test;

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

class Sum  extends RecursiveTask {
    private int arr[];
    private int start;
    private int end;
    private static final int  THRESHOLD = 10;
    public Sum(int[] arr, int start, int end) {
        this.arr = arr;
        this.start = start;
        this.end = end;
    }

    @Override
    protected Integer compute() {

        if(end - start < THRESHOLD) {
            int sum = 0;
            for(int i = start; i < end; i++) {
                sum += arr[i];
            }
            return sum;
        }else {
            int middle = (start + end) / 2;
            Sum lowSum = new Sum(arr,start,middle);
            Sum highSum = new Sum(arr,middle,end);
            //分成两个任务
            lowSum.fork();
            highSum.fork();
            //把两个任务的结果累加合并在一起
            return lowSum.join() + highSum.join();
        }

    }
}
public class TestRecursiveTask {


    public static void main(String[] args) throws ExecutionException, InterruptedException {

        int[] arr = new int[100];
        Random random = new Random();
        int total = 0;
        for(int i = 0; i < arr.length; i++) {
            int temp = random.nextInt(20);
            total += (arr[i] = temp);
        }
        System.out.println(total);
        ForkJoinPool pool = new ForkJoinPool();
        Future future = pool.submit(new Sum(arr,0,arr.length));

        System.out.println(future.get());

    }

}

案例三

多个实现方式的性能比较


import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.stream.LongStream;

import org.junit.Test;

public class TestForkJoinPool {

    public static void main(String[] args) {
        Instant start = Instant.now();

        ForkJoinPool pool = new ForkJoinPool();

        ForkJoinTask task = new ForkJoinSumCalculate(0L, 50000000000L);

        Long sum = pool.invoke(task);

        System.out.println(sum);

        Instant end = Instant.now();

        System.out.println("耗费时间为:" + Duration.between(start, end).toMillis());//166-1996-10590
    }

    @Test
    public void test1(){
        Instant start = Instant.now();

        long sum = 0L;

        for (long i = 0L; i <= 50000000000L; i++) {
            sum += i;
        }

        System.out.println(sum);

        Instant end = Instant.now();

        System.out.println("耗费时间为:" + Duration.between(start, end).toMillis());//35-3142-15704
    }

    //java8 新特性
    @Test
    public void test2(){
        Instant start = Instant.now();

        Long sum = LongStream.rangeClosed(0L, 50000000000L)
                             .parallel()
                             .reduce(0L, Long::sum);

        System.out.println(sum);

        Instant end = Instant.now();

        System.out.println("耗费时间为:" + Duration.between(start, end).toMillis());//1536-8118
    }

}

class ForkJoinSumCalculate extends RecursiveTask{

    /**
     * 
     */
    private static final long serialVersionUID = -259195479995561737L;

    private long start;
    private long end;

    private static final long THURSHOLD = 10000L;  //临界值

    public ForkJoinSumCalculate(long start, long end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        long length = end - start;

        if(length <= THURSHOLD){
            long sum = 0L;

            for (long i = start; i <= end; i++) {
                sum += i;
            }

            return sum;
        }else{
            long middle = (start + end) / 2;

            ForkJoinSumCalculate left = new ForkJoinSumCalculate(start, middle); 
            left.fork(); //进行拆分,同时压入线程队列

            ForkJoinSumCalculate right = new ForkJoinSumCalculate(middle+1, end);
            right.fork(); //

            return left.join() + right.join();
        }
    }

}

注意事项

在使用ForkJoin时需要注意: 如果任务的划分层次很深,一直得不到返回,那么可能出现两种情况:第一,系统内的线程数量越积越多,导致性能严重下降;第二,函数的调用层次变的很深,最终导致栈溢出。


参考

  1. 《疯狂java讲义》–李刚
  2. 尚硅谷多线程
  3. 《实战java高并发程序设计》

你可能感兴趣的:(java多线程)