Fork/Join 并行任务框架

如果现在要你编写计算 1! + 2! + 3! + 4! + … + 20! 的代码,你会怎么做?

最自然的想法是从左边依次向右计算,不断迭代结果。毕竟对于人来说,一心二用还是比较难的,在同一时间只做一件事情是常见的场景。

用代码来实现的话,示例如下:

public class factorial {

    public static int compute(int n) {
        int result = 0;
        for (int i = 1; i <= n; i++) {
            int count = i;
            int temp = 1;
            while (count > 0) {
                temp *= count;
                count--;
            }
            result += temp;
        }
        return result;
    }

    public static void main(String[] args) {
        System.out.println(compute(20));
    }
}

Fork/Join 并行计算

现代的CPU一般都是双核以上,如果支持超线程的话,线程数一般都是4个以上。

每一个线程就相当于一个人,能独立执行任务。如何让多个线程同时工作,加快任务执行执行速度,充分利用CPU性能呢?

JDK 7 中引入的 Fork/Join 并行计算框架可以较好的解决这个问题,它类似于单机版的 MapReduce,都采用了分而治之的思想。

Fork 把一个任务切分为若干子任务并行执行,Join 用来合并这些子任务的执行结果,最终得到任务结果。子任务被分配到不同的核上执行时,效率最高。

Fork/Join 的运行流程图如下:

Fork/Join 使用两个类来完成 Fork 与 Join 两件事情:

  • ForkJoinTask:通过 fork() 方法执行子任务。通常情况下不直接继承 ForkJoinTask 类,针对不同任务,选择 ForkJoinTask 的不同子类来完成任务:
    • RecursiveAction:用于没有返回结果的任务,
    • RecursiveTask :用于有返回结果的任务,通过 join() 方法获取
  • ForkJoinPool :通过 submit() 方法执行 ForkJoinTask 任务,并实现了工作窃取算法
ForkJoinPool主要有如下两个常用构造器:
  • public ForkJoinPool(int parallelism):创建一个包含 parallelism 个并行线程的 ForkJoinPool
  • public ForkJoinPool() :以 Runtime.getRuntime().availableProcessors() 的返回值作为 parallelism 创建 ForkJoinPool

工作窃取算法

工作窃取使得先完成工作的线程可以帮助其它繁忙的线程,让任务更快的得以完成。对于上述例子来说,1! + 2! + 3! + … + 10!就比11! + 12! + 13! + … + 20!完成的更快(当数据量更大时,差异更明显)。

每个工作线程都有自己的双端工作队列。采用双端队列主要是为了减少争用和方便窃取。因为工作线程会从自己的队列头部取任务执行,而其它空闲线程会从工作线程队列尾部取任务执行。

工作窃取算法的优点是充分利用线程进行并行计算,减少了线程间的竞争;缺点是在某些情况下仍然存在竞争(双端队列里只有一个任务时),并且消耗了更多的系统资源(创建了多个线程和多个双端队列)。

对1! + 2! + 3! + 4! + … + 20!通过 Fork/Join 并行任务框架求解,示例如下:

public class ForkJoinDemo extends RecursiveTask{
    private final int THRESHOLD = 10;
    private int start;
    private int end;

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

    @Override
    protected Object compute() {
        if (end - start < THRESHOLD) {
            return factorial(start, end);
        }
        System.out.print("采用Fork/Join求解:");
        int middle = end + start >>> 1;
        ForkJoinDemo left = new ForkJoinDemo(start, middle);
        ForkJoinDemo right = new ForkJoinDemo(middle + 1, end);
        left.fork();
        right.fork();
        return (int)left.join() + (int)right.join();
    }

    public static int factorial(int start, int end) {
        int result = 0;
        for (int i = start; i <= end; i++) {
            int count = i;
            int temp = 1;
            while (count > 0) {
                temp *= count;
                count--;
            }
            result += temp;
        }
        return result;
    }

    public static void main(String[] args) {
        ForkJoinPool pool = new ForkJoinPool();
        Future<Integer> result = pool.submit(new ForkJoinDemo(1,20));
        try {
            System.out.println(result.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

输出结果:

采用Fork/Join求解:268040729

猜你喜欢

转载自blog.csdn.net/weixin_43320847/article/details/83239809