Java Fork and Join using ForkJoinPool

本文翻译自http://tutorials.jenkov.com/java-util-concurrent/java-fork-and-join-forkjoinpool.html,人工翻译,仅供学习交流。

Java Fork and Join using ForkJoinPool

在Java 7中添加了ForkJoinPool,ForkJoinPool类似于Java ExecutorService,但有一个区别。ForkJoinPool使任务很容易把他们的工作分割成更小的可以提交给ForkJoinPool的任务。只要任务能够拆分,可以继续将它们的工作拆分为更小的子任务。这听起来可能有点抽象,所以在这个分叉和加入教程中,我将解释ForkJoinPool如何工作,以及如何拆分任务。

Fork和Join的解释

在我们看ForkJoinPool之前,我想解释一下fork和join原理是如何工作的。fork和join原理由递归执行的两个步骤组成。这两个步骤是fork步骤和join步骤。

Fork

使用fork和join原理的任务可以将自己拆分为更小的可以并发执行的子任务,如下图所示:在这里插入图片描述
通过将自己分解成子任务,每个子任务可以由不同的cpu并行执行,或者是同一个CPU上的不同线程。只有当任务分配的工作足够大时,任务才会将自己分解成子任务。将一个任务分解成子任务会产生开销,因此,对于少量的工作,这个开销可能比并发执行子任务获得的加速更大。
将一个任务拆分为子任务有意义的限制也称为阈值。由每个任务来决定一个合理的阈值。这在很大程度上取决于工作的类型。

Join

当一个任务将自己分解成子任务时,该任务等待所有子任务完成执行。
一旦子任务完成执行,任务可以将所有结果合并为一个结果。如下图所示:在这里插入图片描述
当然,不是所有类型的任务都可以返回结果。如果任务不返回结果,那么任务只会等待它的子任务完成,这时不会发生结果合并。

ForkJoinPool

ForkJoinPool是一个设计用来很好地处理fork-and-join任务分割的特殊线程池,ForkJoinPool位于java.util.concurrent包中,所以完整的类名是java.util.concurrent.ForkJoinPool。

创建ForkJoinPool

使用ForkJoinPool的构造函数创建ForkJoinPool。作为ForkJoinPool构造函数的参数可以指定需要的并行级别。并行级别是指在传递给ForkJoinPool的任务上,希望并发运行多少线程或cpu。以下是一个创建ForkJoinPool的示例:

ForkJoinPool forkJoinPool = new ForkJoinPool(4);

这个例子创建了一个并行级别为4的ForkJoinPool。

提交任务到ForkJoinPool

将任务提交给ForkJoinPool类似于将任务提交给ExecutorService。您可以提交两种类型的任务,不返回任何结果的任务(一个“动作”)和一个返回结果的任务(一个“任务”)。这两种类型的任务由RecursiveAction和RecursiveTask类表示。以下部分将介绍如何使用这两个任务以及如何提交它们。

RecursiveAction类

RecursiveAction是一个不返回任何值的任务,它只是做一些工作,例如将数据写入磁盘,然后退出。
RecursiveAction可能仍然需要将其工作分解成更小的由独立的线程或cpu执行的块。
通过子类化递归动作来实现递归动作。这里是一个RecursiveAction的例子:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.RecursiveAction;

public class MyRecursiveAction extends RecursiveAction {
    private long workLoad = 0;
    public MyRecursiveAction(long workLoad) {
        this.workLoad = workLoad;
    }

    @Override
    protected void compute() {

        //if work is above threshold, break tasks up into smaller tasks
        if(this.workLoad > 16) {
            System.out.println("Splitting workLoad : " + this.workLoad);

            List<MyRecursiveAction> subtasks =
                new ArrayList<MyRecursiveAction>();

            subtasks.addAll(createSubtasks());

            for(RecursiveAction subtask : subtasks){
                subtask.fork();
            }

        } else {
            System.out.println("Doing workLoad myself: " + this.workLoad);
        }
    }

    private List<MyRecursiveAction> createSubtasks() {
        List<MyRecursiveAction> subtasks =
            new ArrayList<MyRecursiveAction>();

        MyRecursiveAction subtask1 = new MyRecursiveAction(this.workLoad / 2);
        MyRecursiveAction subtask2 = new MyRecursiveAction(this.workLoad / 2);

        subtasks.add(subtask1);
        subtasks.add(subtask2);

        return subtasks;
    }

}

这个例子非常简单,MyRecursiveAction只是将一个虚构的工作负载作为其构造函数的参数。如果工作负载超过某个阈值,工作被分解成子任务,这些子任务也被调度执行(通过子任务的fork()方法)。如果工作负载低于某个阈值,则工作将由MyRecursiveAction 自身执行。
你可以像这样调度MyRecursiveAction的执行:

MyRecursiveAction myRecursiveAction = new MyRecursiveAction(24);

forkJoinPool.invoke(myRecursiveAction);

RecursiveTask类

RecursiveTask是一个会返回结果的任务。它可能会把工作分成更小的任务,然后把这些小任务的结果合并成一个集体的结果。拆分和合并可能发生在几个层次上。下面是一个RecursiveTask的例子:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.RecursiveTask;
    
public class MyRecursiveTask extends RecursiveTask<Long> {
    private long workLoad = 0;
    public MyRecursiveTask(long workLoad) {
        this.workLoad = workLoad;
    }
    protected Long compute() {

        //if work is above threshold, break tasks up into smaller tasks
        if(this.workLoad > 16) {
            System.out.println("Splitting workLoad : " + this.workLoad);

            List<MyRecursiveTask> subtasks =
                new ArrayList<MyRecursiveTask>();
            subtasks.addAll(createSubtasks());

            for(MyRecursiveTask subtask : subtasks){
                subtask.fork();
            }

            long result = 0;
            for(MyRecursiveTask subtask : subtasks) {
                result += subtask.join();
            }
            return result;

        } else {
            System.out.println("Doing workLoad myself: " + this.workLoad);
            return workLoad * 3;
        }
    }

    private List<MyRecursiveTask> createSubtasks() {
        List<MyRecursiveTask> subtasks =
        new ArrayList<MyRecursiveTask>();

        MyRecursiveTask subtask1 = new MyRecursiveTask(this.workLoad / 2);
        MyRecursiveTask subtask2 = new MyRecursiveTask(this.workLoad / 2);

        subtasks.add(subtask1);
        subtasks.add(subtask2);

        return subtasks;
    }
}

这个示例与RecursiveAction示例相似,不同的是它返回一个结果。MyRecursiveTask类扩展了RecursiveTask意味着任务返回的结果是一个Long类型。
MyRecursiveTask示例还将工作分解为子任务,并使用它们的fork()方法调度这些子任务的执行。
然后,这个示例通过调用每个子任务的join()方法接收每个子任务返回的结果,子任务结果合并为更大的结果,然后返回更大的结果。这种子任务结果的连接/合并可能会在多个级别的递归水平上递归地发生。
你可以这样调度一个RecursiveTask:

MyRecursiveTask myRecursiveTask = new MyRecursiveTask(128);

long mergedResult = forkJoinPool.invoke(myRecursiveTask);

System.out.println("mergedResult = " + mergedResult); 

注意如何从ForkJoinPool.invoke()方法调用中获得最终结果。

ForkJoinPool批判

似乎不是每个人都对Java 7中的新的ForkJoinPool感到满意。在搜索关于ForkJoinPool的经验和观点时,我遇到了以下情况

下一节:Java Lock
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值