Java并发编程之ForkJoinPool

149 阅读2分钟

前言

ForkJoinPool是一种线程池,它和其他线程池的不同之处在于,它主要体现的是分治思想,和之前提到的LongAdder的原理有点相似,就是把一个大任务拆分成很多小任务去处理,最后再对所有执行结果进行汇总,这样就可以使用多线程技术来提高效率。

原理

ForkJoinPool之所以高效还有一个原因是它采用的是工作窃取(work-stealing)的模式,ThreadPoolExecutor线程池的实现是多个线程共享一个任务队列,而工作窃取模式每个线程都有一个双端队列Deque,当一个线程队列中的任务执行完之后,会去其他线程的队列末尾取任务执行,工作窃取模式主要有以下几个特点

  • 每个线程都有一个双端队列
  • 每个线程执行完自己队列中的任务之后,会去别的线程的队列末尾窃取任务执行,充分利用CPU资源
  • 从尾部窃取任务可以有效降低线程之间的竞争问题
  • 窃取的任务队列中只有一个任务时,会通过CAS方式同原线程进行竞争

用法

下面通过一个数字求和功能来介绍下ForkJoinPool的用法

// 自定义类继承任务类
public class MyTask extends RecursiveTask<Integer> {
    private int n;

    public MyTask(int n) {
        this.n = n;
    }
    
    @Override
    protected Integer compute() {
        if (n == 1) {  // 拆分终止条件
            return 1;
        }
        MyTask myTask = new MyTask(n - 1); // 创建子任务
        myTask.fork();  // 拆分任务
        return n + myTask.join(); // join等待返回结果
    }

    public static void main(String[] args) {
        ForkJoinPool forkJoinPool = new ForkJoinPool();  // 创建线程池
        Integer result = forkJoinPool.invoke(new MyTask(5));  // 执行任务
        System.out.println(result);
    }
}

ForkJoinPool执行的任务类型是ForkJoinTask,它有两个子类分别是没有返回值的RecursiveAction和有返回值的RecursiveTask,根据实际业务需要来决定使用哪个类。

自定义任务类继承这两个任务类,然后重写compute方法来拆分任务,其实和递归的做法很相似,上面是求数字1~n的和,首先有一个终止拆分的条件就是n=1了,然后task(n)可以把任务拆分成ntask(n-1)task(n-1)又可以拆分成n-1task(n-2)......依次类推进行拆分。fork的作用就是拆分任务,而join的作用就是等待任务结束返回结果。

应用

Java8中的并行流(parallelStream)和 CompletableFuture,都是基于ForkJoinPool实现的。