Executor Framework分析 (五) ForkJoinPool的使用

本文详细介绍了ForkJoinPool的工作原理,包括Fork/Join框架、Work-Stealing算法,以及与ThreadPoolExecutor的区别。通过一个计算π的例子展示了ForkJoinPool的使用,并强调了临界值选择的重要性。
摘要由CSDN通过智能技术生成

本篇博客记录一下ForkJoinPool相关的内容,主要涉及基本的设计思想和使用方式。


一、前言

Fork/Join
ForkJoinPool继承自AbstractExecutorService,是JDK1.7引入的并行处理框架,
作为Fork/Join型线程池的实现。

ForkJoinPool的基本思想是:
将大任务分割(Fork)成多个小任务;
多个子任务可以被多个线程并发执行;
最后将子任务聚合(Join)起来作为大任务的结果。

盗图一张,ForkJoinPool执行任务的思想类似于,实际上有点分治递归的感觉:

为了支持这种能力,JDK 1.7中专门定义了对应的任务类型ForkJoinTask。
不过我们一般不需要直接使用ForkJoinTask,仅需要继承它的子类RecursiveAction或RecursiveTask,
并实现对应的抽象方法compute即可。

其中,RecursiveAction是不带返回值的Fork/Join型任务,使用此类任务时并不产生结果,也就不涉及到结果的合并;
而RecursiveTask是带返回值的Fork/Join型任务,使用此类任务需要我们进行结果的合并。

Work-Stealing
相比于ThreadPoolExecutor,ForkJoinPool并发处理子任务时,引入了Work-Stealing算法。

Work-Stealing的思想是:
线程池中的每个线程,都有一个与之关联的任务队列(双端队列)。
线程每次都优先从与自己关联队列的头部取出任务来运行。

如果某个线程执行完当前任务后,发现关联的队列已空,
就会尝试从其它线程关联队列的尾部“窃取”任务来执行。

Work-Stealing算法的优点是充分利用线程进行并行计算,一定程度上减少了线程间的竞争,
适用于不同任务耗时相差比较大的场景。

与ThreadPoolExecutor对比
ForkJoinPool和ThreadPoolExecutor都是线程池,它们之间的不同点在于:
ThreadPoolExecutor只能执行Runnable和Callable任务,
而ForkJoinPool不仅可以执行Runnable和Callable 任务,
还可以执行ForkJoinTask,从而满足并行地实现分治算法的需要。

ThreadPoolExecutor中,任务的执行顺序是FIFO的,
所以后面的任务需要等待前面任务开始执行后才能被取出;

而ForkJoinPool每个线程有自己的任务队列,并在此基础上实现了Work-Stealing的功能,
使得在某些情况下,ForkJoinPool能更大程度的提高并发效率。

不过,如果ForkJoinPool需要执行的任务耗时很平均,
由于窃取任务时不同线程需要抢占锁,有可能会造成额外的时间消耗。
此外,Work-Stealing算法中,每个线程都需要维护双端队列,
这也会造成较大的内存消耗。

因此整体来讲,ForkJoinPool和ThreadPoolExecutor各有千秋,
需结合具体的业务场景来使用。


二、构造函数
接下来,我们看看ForkJoinPool的构造函数:

    //无参数时,ForkJoinPool的线程数上限取决于处理器的数量
    //使用的线程工厂类型为DefaultForkJoinWorkerThreadFactory
    public ForkJoinPool() {
        this(Math.min(MAX_CAP, Runtime.getRuntime()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值