Java7 Fork-Join 框架:任务切分,并行处理

概要

现代的计算机已经向多CPU方向发展,即使是普通的PC,甚至现在的智能手机、多核处理器已被广泛应用。在未来,处理器的核心数将会发展的越来越多。
虽然硬件上的多核CPU已经十分成熟,但是很多应用程序并未这种多核CPU做好准备,因此并不能很好地利用多核CPU的性能优势。
为了充分利用多CPU、多核CPU的性能优势,级软基软件系统应该可以充分“挖掘”每个CPU的计算能力,决不能让某个CPU处于“空闲”状态。为此,可以考虑把一个任务拆分成多个“小任务”,把多个"小任务"放到多个处理器核心上并行执行。当多个“小任务”执行完成之后,再将这些执行结果合并起来即可。


Java在JDK7之后加入了并行计算的框架Fork/Join,可以解决我们系统中大数据计算的性能问题。Fork/Join采用的是分治法,Fork是将一个大任务拆分成若干个子任务,子任务分别去计算,而Join是获取到子任务的计算结果,然后合并,这个是递归的过程。子任务被分配到不同的核上执行时,效率最高。伪代码如下:

Result solve(Problem problem) {
    if (problem is small)
        directly solve problem
    else {
        split problem into independent parts
        fork new subtasks to solve each part
        join all subtasks
        compose result from subresults
    }
}

Fork/Join框架的核心类是ForkJoinPool,它能够接收一个ForkJoinTask,并得到计算结果。ForkJoinTask有两个子类,RecursiveTask(有返回值)和RecursiveAction(无返回结果),我们自己定义任务时,只需选择这两个类继承即可

示例代码

package forkJoin;

import java.util.concurrent.RecursiveTask;

public class SumTask extends RecursiveTask<Integer> {
    private static final int THRESHOLD = 20;

    private int[] array;
    private int low;
    private int high;

    public SumTask(int[] array, int low, int high) {
        this.array = array;
        this.low = low;
        this.high = high;
    }

    @Override
    protected Integer compute() {
        int sum = 0;
        if (high - low + 1 <= THRESHOLD) {
            System.out.println(low + " - " + high + "  计算");
//            测试并行的个数,统计输出过程中的文字,看看有多少线程停止在这里就知道有多少并行计算
//            参考 ForkJoinPool 初始化设置的并行数
//            try {
//                Thread.sleep(11111111);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
            // 小于阈值则直接计算
            for (int i = low; i <= high; i++) {
                sum += array[i];
            }
        } else {
            System.out.println(low + " - " + high + "  切分");
            // 1. 一个大任务分割成两个子任务
            int mid = (low + high) / 2;
            SumTask left = new SumTask(array, low, mid);
            SumTask right = new SumTask(array, mid + 1, high);

            // 2. 分别并行计算
            invokeAll(left, right);

            // 3. 合并结果
            sum = left.join() + right.join();

            // 另一种方式
            try {
                sum = left.get() + right.get();
            } catch (Throwable e) {
                System.out.println(e.getMessage());
            }
        }
        return sum;
    }
}
package forkJoin;

import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;

public class Main {

    /*static class MyTaskTest extends RecursiveTask<Integer> {
        final int n;

        MyTaskTest(int n) {
            this.n = n;
        }

        @Override
        protected Integer compute() {
            if (n <= 1) return n;
            MyTaskTest f1 = new MyTaskTest(n - 1);
            f1.fork();
            MyTaskTest f2 = new MyTaskTest(n - 2);
            return f2.compute() + f1.join();
        }
    }*/

    /*class SortTask extends RecursiveAction {
        static final int THRESHOLD = 2;
        final long[] array;
        final int lo;
        final int hi;

        SortTask(long[] array, int lo, int hi) {
            this.array = array;
            this.lo = lo;
            this.hi = hi;
        }

        protected void compute() {
            if (hi - lo < THRESHOLD)
                sequentiallySort(array, lo, hi);
            else {
                int mid = (lo + hi) >>> 1;
                invokeAll(new SortTask(array, lo, mid),
                        new SortTask(array, mid, hi));
                merge(array, lo, hi);
            }
        }
    }*/

    private static int[] genArray() {
        int[] array = new int[100];
        for (int i = 0; i < array.length; i++) {
            array[i] = new Random().nextInt(500);
        }
        return array;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        /**
         * 下面以一个有返回值的大任务为例,介绍一下RecursiveTask的用法。
         大任务是:计算随机的100个数字的和。
         小任务是:每次只能20个数值的和。
         */
        int[] array = genArray();

//        System.out.println(Arrays.toString(array));
        int total = 0;
        for (int i = 0; i < array.length; i++) {
            total += array[i];
        }
        System.out.println("目标和:" + total);

        // 1. 创建任务
        SumTask sumTask = new SumTask(array, 0, array.length - 1);

        // 2. 创建线程池
        // 设置并行计算的个数
        int processors = Runtime.getRuntime().availableProcessors();
        ForkJoinPool forkJoinPool = new ForkJoinPool(processors * 2);

        // 3. 提交任务到线程池
        forkJoinPool.submit(sumTask);
//        forkJoinPool.shutdown();

        long begin = System.currentTimeMillis();
        // 4. 获取结果
        Integer result = sumTask.get();// wait for
        long end = System.currentTimeMillis();
        System.out.println(String.format("结果 %s ,耗时 %sms", result, end - begin));

        if (result == total) {
            System.out.println("测试成功");
        } else {
            System.out.println("fork join 使用失败!!!!");
        }
    }
}

上面的代码是一个100个整数累加的任务,切分到小于20个数的时候直接进行累加,不再切分。
我们通过调整阈值(THRESHOLD),可以发现耗时是不一样的。实际应用中,如果需要分割的任务大小是固定的,可以经过测试,得到最佳阈值;如果大小不是固定的,就需要设计一个可伸缩的算法,来动态计算出阈值。如果子任务很多,效率并不一定会高。 
PS:类似的这种“分而治之”的需求场景,往往带有递归性,实际中,我们可以考虑任务是否具有“递归性”来决定是否使用“Fork-Join”框架。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据你提供的`package.json`文件,可以看出你的项目依赖关系如下: 开发依赖项: - `@types/jest`: 用于为Jest测试框架提供TypeScript类型定义。 - `@types/jquery`: 用于为jQuery库提供TypeScript类型定义。 - `@types/source-map`: 用于为source-map库提供TypeScript类型定义。 - `@typescript-eslint/eslint-plugin`: 用于在ESLint中使用TypeScript规则的插件。 - `@typescript-eslint/parser`: 用于解析TypeScript代码并生成抽象语法树(AST),供ESLint使用。 - `awesome-typescript-loader`: 用于Webpack中加载TypeScript文件。 - `clean-webpack-plugin`: 用于在每次构建之前清理输出目录。 - `eslint`: 用于进行代码规范检查。 - `fork-ts-checker-webpack-plugin`: 用于在Webpack构建过程中进行TypeScript类型检查。 - `html-webpack-plugin`: 用于生成HTML文件并自动引入打包后的资源。 - `jest`: 用于进行单元测试。 - `ts-jest`: 用于在Jest中解析和运行TypeScript代码。 - `ts-loader`: 用于Webpack中加载TypeScript文件。 - `typescript`: TypeScript编译器。 - `webpack`: 用于打包项目资源。 - `webpack-cli`: 用于在命令行中运行Webpack命令。 - `webpack-dev-server`: 用于在开发过程中提供一个开发服务器,支持热更新和自动刷新。 - `webpack-merge`: 用于合并多个Webpack配置文件。 生产依赖项: - `jquery`: jQuery库。 - `moment`: 日期时间操作库。 根据你提供的信息,你的`npm start`命令应该是可以正常工作的。但是,记得在运行之前,先根据之前的回答中的建议,升级Node.js版本到npm支持的版本。 如果你遇到了其他问题,请提供错误信息或详细描述你的操作步骤,以便更好地帮助你解决问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值