通俗易懂的JUC源码剖析-ForkJoinPool

前言

ForkJoinPool常用于将大任务分解(Fork)成若干小任务并行执行,然后再把每个小任务的执行结果合并起来(Join)得到大任务的最终结果。下面是示意图(ps:盗网上网上盗的图,禁止套娃!)
通俗易懂的JUC源码剖析-ForkJoinPool_第1张图片
ForkJoinPool通常配合ForkJoinTask一起使用,ForkJoinTask代表一个任务,它是个抽象类,它的常见子类有RecursiveTask和RecursiveAction,其中RecursiveTask有返回值,RecursiveAction无返回值。

下面举个简单栗子来说明ForkJoinPool的使用场景。
场景:计算整数1~10000000的和。

最传统的方式就是直接for循环累加,但这里我们也可以用ForkJoinPool实现并行计算,以此提升性能(数据量很大时)。代码如下:

import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
import java.util.stream.LongStream;
public class ForkJoinPoolDemo {
    public static void main(String[] args) {
        long[] nums = LongStream.rangeClosed(1, 10000000).toArray();
        ForkJoinPool pool = new ForkJoinPool();
        Instant before = Instant.now();
        Long result = pool.invoke(new ComputeSumTask(nums, 0, nums.length - 1));
        Instant after = Instant.now();
        pool.shutdown();
        System.out.println(result);
        System.out.println("cost time(ms) : " + Duration.between(before, after).toMillis());
        System.out.println("Now let's watch traditional for-loop cost time below :");
        before = Instant.now();
        result = forLoopCompute(nums);
        after = Instant.now();
        System.out.println(result);
        System.out.println("cost time(ms) : " + Duration.between(before, after).toMillis());
 }
    private static Long forLoopCompute(long[] nums) {
        long sum = 0;
        for (long num : nums) {
             sum += num;
        }
        return sum;
    }
    static class ComputeSumTask extends RecursiveTask {
        private int start;
        private int end;
        private long[] nums;
        ComputeSumTask(long[] nums, int start, int end) {
             this.nums = nums;
             this.start = start;
             this.end = end;
        }
        @Override
        protected Long compute() {
            // 当需要计算的数字个数小于10000时,退化成直接for循环计算
            // 注意这里的阈值要合适,太小的话容易导致内存溢出,太大的话发挥不了ForkJoinPool的优势。
            if (end - start < 10000) {
                long sum = 0;
                for (int i = start; i <= end; i++) {
                    sum += nums[i];
                }
                return sum;
            } else {
                int mid = (end - start) / 2 + start;
                ComputeSumTask leftTask = new ComputeSumTask(nums, start, mid);
                ComputeSumTask rightTask = new ComputeSumTask(nums, mid + 1, end);
                leftTask.fork();
                rightTask.fork();
                return leftTask.join() + rightTask.join();
            }
        }
    }
}

输出结果:
通俗易懂的JUC源码剖析-ForkJoinPool_第2张图片

结果很奇怪,传统for循环耗时反而更短,这是因为10000000数据量还不够大,没有发挥ForkJoinPool的优势,并且由于fork和join的操作反而消耗了性能。我们再加个0看看效果。

通俗易懂的JUC源码剖析-ForkJoinPool_第3张图片
可以看到,这时候使用ForkJoinPool性能就提升了。

实现原理

先看看类图结构:

@sun.misc.Contended
public class ForkJoinPool extends AbstractExecutorService {
}

它跟ThreadPoolExecutor一样,也继承了AbstractExecutorService,说明它也是一种线程池。

再来看看关键属性:

明天再分析,先去跟妹子聊天了,嘻嘻~

你可能感兴趣的:(java)