JUC 基础10—— Fork-Join 框架

Fork-Join 框架

概述

Fork\Join 框架是 JDK1.7 加入的一种Java并发编程模型,它体现的是一种分治思想, 它是基于工作窃取(work-stealing)算法的一种并发框架,适用于能够进行任务拆分的 cpu 密集型运算。

所谓任务拆分,是将一个大任务拆分为算法上相同的小任务,直至不能拆分可以直接求解。例如:和递归相关的一些计算,如归并排序、斐波那契数列等都可以用分治思想进行求解。

Fork-Join 框架的核心就是将大任务拆解成多个小任务,然后将这些小任务分配给不同的线程进行并行执行。当一个线程执行完自己的任务后,它可以从其他线程的任务队列中"偷取"一个任务来执行,以避免线程出现闲置等待的情况。

Fork/Join 框架的主要组件是 ForkJoinPoolForkJoinTaskRecursiveTask(用于有返回值的任务)或 RecursiveAction(用于无返回值的任务)。

使用Fork-Join

提交给 Fork-Join 线程池的任务需要继承 RecursiveTask(有返回值)或 RecursiveAction(没有返回值),

案例1:对 1~n 之间的整数求和

java 复制代码
@Slf4j
public class ForkJoinDemo {
    public static void main(String[] args) {
        ForkJoinPool p = new ForkJoinPool(8);//指定线程池的工作线程数,如果不指定,默认工作线程数和CPU核数一样
        System.out.println(p.invoke(new MyTask(10)));//使用invoke提交并获取到结果
    }
}

@Data
@Slf4j
class MyTask extends RecursiveTask<Integer> {
    private int n;

    public MyTask(int n){
        this.n =n;
    }

    @Override
    public String toString() {
        return "{" + n + '}';
    }


    /**
     * The main computation performed by this task.
     *
     * @return the result of the computation
     */
    @Override
    protected Integer compute() {
        System.out.println("开始进行从1 "+ " 到 "+n+" 累加任务");
        if(n==1){
            log.debug("join() {}", n);
            return n;
        }
        MyTask t = new MyTask(n-1);
        t.fork();
        log.debug("fork() {} + {}", n, t);

        return n+t.join();
    }
}

输出结果:

css 复制代码
开始进行从1  到 10 累加任务
开始进行从1  到 9 累加任务
开始进行从1  到 8 累加任务
开始进行从1  到 7 累加任务
开始进行从1  到 6 累加任务
开始进行从1  到 5 累加任务
开始进行从1  到 4 累加任务
开始进行从1  到 3 累加任务
10:41:55.500 [ForkJoinPool-1-worker-2] DEBUG com.avgrado.demo.thread.MyTask - fork() 9 + {8}
10:41:55.500 [ForkJoinPool-1-worker-3] DEBUG com.avgrado.demo.thread.MyTask - fork() 8 + {7}
10:41:55.500 [ForkJoinPool-1-worker-0] DEBUG com.avgrado.demo.thread.MyTask - fork() 3 + {2}
10:41:55.500 [ForkJoinPool-1-worker-5] DEBUG com.avgrado.demo.thread.MyTask - fork() 6 + {5}
开始进行从1  到 2 累加任务
10:41:55.500 [ForkJoinPool-1-worker-7] DEBUG com.avgrado.demo.thread.MyTask - fork() 4 + {3}
10:41:55.500 [ForkJoinPool-1-worker-4] DEBUG com.avgrado.demo.thread.MyTask - fork() 7 + {6}
开始进行从1  到 1 累加任务
10:41:55.500 [ForkJoinPool-1-worker-6] DEBUG com.avgrado.demo.thread.MyTask - fork() 5 + {4}
10:41:55.513 [ForkJoinPool-1-worker-0] DEBUG com.avgrado.demo.thread.MyTask - fork() 2 + {1}
10:41:55.500 [ForkJoinPool-1-worker-1] DEBUG com.avgrado.demo.thread.MyTask - fork() 10 + {9}
10:41:55.513 [ForkJoinPool-1-worker-7] DEBUG com.avgrado.demo.thread.MyTask - join() 1
55

案例2:对 1~1000之间的整数求和,每100切分一次,最后叠加求结果

java 复制代码
@Slf4j
public class ForkJoinDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CacluTask task = new CacluTask(1,1000);
        ForkJoinPool pool = new ForkJoinPool();
        ForkJoinTask<Long> data = pool.submit(task);//使用submit提交
        System.out.println(data.get());
    }
}

@Data
@Slf4j
class CacluTask extends RecursiveTask<Long> {
    private int start;
    private int end;
    private long result;

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


    /**
     * The main computation performed by this task.
     *
     * @return the result of the computation
     */
    @Override
    protected Long compute() {

        //完成1+1000,每100累加
        if(end -start <=100){
            log.debug("开始进行累加从 "+start+" 到 "+end+" 累加任务");
            for(int i = start;i<=end;i++){
                result +=i;
            }
            log.debug("join()  {}",result);
        }else {
            int middle = start + 100;
            //递归调用
            CacluTask  task1 = new CacluTask(start,middle-1);
            CacluTask task2 = new CacluTask(middle,end);
            task1.fork();
            log.debug("fork() {} {} {} {}",start,middle,end,task1);
            task2.fork();
            log.debug("fork() {} {} {} {}",start,middle,end,task2);
            //同步执行结果
            result = task1.join() + task2.join();
        }
        return result;
    }
}

计算结果:

ini 复制代码
10:50:22.025 [ForkJoinPool-1-worker-1] DEBUG com.avgrado.demo.thread.CacluTask - fork() 1 101 1000 CacluTask(start=1, end=100, result=0)
10:50:22.025 [ForkJoinPool-1-worker-2] DEBUG com.avgrado.demo.thread.CacluTask - 开始进行累加从 1 到 100 累加任务
10:50:22.029 [ForkJoinPool-1-worker-2] DEBUG com.avgrado.demo.thread.CacluTask - join()  5050
10:50:22.029 [ForkJoinPool-1-worker-1] DEBUG com.avgrado.demo.thread.CacluTask - fork() 1 101 1000 CacluTask(start=101, end=1000, result=0)
10:50:22.029 [ForkJoinPool-1-worker-1] DEBUG com.avgrado.demo.thread.CacluTask - 开始进行累加从 101 到 200 累加任务
10:50:22.029 [ForkJoinPool-1-worker-2] DEBUG com.avgrado.demo.thread.CacluTask - fork() 101 201 1000 CacluTask(start=101, end=200, result=15050)
10:50:22.029 [ForkJoinPool-1-worker-1] DEBUG com.avgrado.demo.thread.CacluTask - join()  15050
10:50:22.029 [ForkJoinPool-1-worker-2] DEBUG com.avgrado.demo.thread.CacluTask - fork() 101 201 1000 CacluTask(start=201, end=1000, result=0)
10:50:22.029 [ForkJoinPool-1-worker-4] DEBUG com.avgrado.demo.thread.CacluTask - fork() 201 301 1000 CacluTask(start=201, end=300, result=0)
10:50:22.029 [ForkJoinPool-1-worker-3] DEBUG com.avgrado.demo.thread.CacluTask - 开始进行累加从 201 到 300 累加任务
10:50:22.029 [ForkJoinPool-1-worker-3] DEBUG com.avgrado.demo.thread.CacluTask - join()  25050
10:50:22.029 [ForkJoinPool-1-worker-3] DEBUG com.avgrado.demo.thread.CacluTask - 开始进行累加从 301 到 400 累加任务
10:50:22.029 [ForkJoinPool-1-worker-4] DEBUG com.avgrado.demo.thread.CacluTask - fork() 201 301 1000 CacluTask(start=301, end=1000, result=0)
10:50:22.029 [ForkJoinPool-1-worker-3] DEBUG com.avgrado.demo.thread.CacluTask - join()  35050
10:50:22.029 [ForkJoinPool-1-worker-2] DEBUG com.avgrado.demo.thread.CacluTask - fork() 301 401 1000 CacluTask(start=301, end=400, result=35050)
10:50:22.029 [ForkJoinPool-1-worker-3] DEBUG com.avgrado.demo.thread.CacluTask - 开始进行累加从 401 到 500 累加任务
10:50:22.029 [ForkJoinPool-1-worker-2] DEBUG com.avgrado.demo.thread.CacluTask - fork() 301 401 1000 CacluTask(start=401, end=1000, result=0)
10:50:22.029 [ForkJoinPool-1-worker-3] DEBUG com.avgrado.demo.thread.CacluTask - join()  45050
10:50:22.029 [ForkJoinPool-1-worker-4] DEBUG com.avgrado.demo.thread.CacluTask - fork() 401 501 1000 CacluTask(start=401, end=500, result=45050)
10:50:22.029 [ForkJoinPool-1-worker-4] DEBUG com.avgrado.demo.thread.CacluTask - fork() 401 501 1000 CacluTask(start=501, end=1000, result=0)
10:50:22.029 [ForkJoinPool-1-worker-2] DEBUG com.avgrado.demo.thread.CacluTask - fork() 501 601 1000 CacluTask(start=501, end=600, result=0)
10:50:22.029 [ForkJoinPool-1-worker-6] DEBUG com.avgrado.demo.thread.CacluTask - 开始进行累加从 501 到 600 累加任务
10:50:22.029 [ForkJoinPool-1-worker-3] DEBUG com.avgrado.demo.thread.CacluTask - 开始进行累加从 601 到 700 累加任务
10:50:22.029 [ForkJoinPool-1-worker-4] DEBUG com.avgrado.demo.thread.CacluTask - fork() 601 701 1000 CacluTask(start=601, end=700, result=0)
10:50:22.029 [ForkJoinPool-1-worker-6] DEBUG com.avgrado.demo.thread.CacluTask - join()  55050
10:50:22.029 [ForkJoinPool-1-worker-3] DEBUG com.avgrado.demo.thread.CacluTask - join()  65050
10:50:22.029 [ForkJoinPool-1-worker-2] DEBUG com.avgrado.demo.thread.CacluTask - fork() 501 601 1000 CacluTask(start=601, end=1000, result=0)
10:50:22.029 [ForkJoinPool-1-worker-6] DEBUG com.avgrado.demo.thread.CacluTask - 开始进行累加从 701 到 800 累加任务
10:50:22.029 [ForkJoinPool-1-worker-4] DEBUG com.avgrado.demo.thread.CacluTask - fork() 601 701 1000 CacluTask(start=701, end=1000, result=0)
10:50:22.029 [ForkJoinPool-1-worker-7] DEBUG com.avgrado.demo.thread.CacluTask - fork() 701 801 1000 CacluTask(start=701, end=800, result=0)
10:50:22.029 [ForkJoinPool-1-worker-6] DEBUG com.avgrado.demo.thread.CacluTask - join()  75050
10:50:22.029 [ForkJoinPool-1-worker-3] DEBUG com.avgrado.demo.thread.CacluTask - 开始进行累加从 801 到 900 累加任务
10:50:22.029 [ForkJoinPool-1-worker-7] DEBUG com.avgrado.demo.thread.CacluTask - fork() 701 801 1000 CacluTask(start=801, end=1000, result=0)
10:50:22.029 [ForkJoinPool-1-worker-4] DEBUG com.avgrado.demo.thread.CacluTask - fork() 801 901 1000 CacluTask(start=801, end=900, result=0)
10:50:22.029 [ForkJoinPool-1-worker-3] DEBUG com.avgrado.demo.thread.CacluTask - join()  85050
10:50:22.029 [ForkJoinPool-1-worker-7] DEBUG com.avgrado.demo.thread.CacluTask - 开始进行累加从 901 到 1000 累加任务
10:50:22.029 [ForkJoinPool-1-worker-4] DEBUG com.avgrado.demo.thread.CacluTask - fork() 801 901 1000 CacluTask(start=901, end=1000, result=0)
10:50:22.029 [ForkJoinPool-1-worker-7] DEBUG com.avgrado.demo.thread.CacluTask - join()  95050
500500

从上述的两个案例中可以看到ForkJoinPool提交任务的方式有两种:

  • submit:可提交 Callable 、ForkJoinTask、Runnable 类型的任务
  • invoke:可提交 ForkJoinTask 类型的任务 代码中提交给 Fork-Join 线程池的任务类继承的 RecursiveTask 是 ForkJoinTask的子类, RecursiveAction 也是ForkJoinTask的子类

需要注意的点

Fork-Join 框架适合处理一些比较大型的任务,如果是小量级别的任务,Fork-Join的处理效率就显得并不高效了。这是因为Fork-Join框架在拆分任务和合并结果都需要时间,而且开启多个线程处理任务时也涉及到CPU的切换耗时。这种情况下就不适合使用 Fork-Join框架

相关推荐
FF在路上34 分钟前
Knife4j调试实体类传参扁平化模式修改:default-flat-param-object: true
java·开发语言
真的很上进41 分钟前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
众拾达人1 小时前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言
皓木.1 小时前
Mybatis-Plus
java·开发语言
不良人天码星1 小时前
lombok插件不生效
java·开发语言·intellij-idea
守护者1702 小时前
JAVA学习-练习试用Java实现“使用Arrays.toString方法将数组转换为字符串并打印出来”
java·学习
源码哥_博纳软云2 小时前
JAVA同城服务场馆门店预约系统支持H5小程序APP源码
java·开发语言·微信小程序·小程序·微信公众平台
禾高网络2 小时前
租赁小程序成品|租赁系统搭建核心功能
java·人工智能·小程序
学会沉淀。2 小时前
Docker学习
java·开发语言·学习
如若1232 小时前
对文件内的文件名生成目录,方便查阅
java·前端·python