java中的fork、join是什么?这个fork和我们常见到的如“fork一个进程去执行xxx”是一回事么?

首先,说一下fork:

  • 操作系统中的 Fork

        在Unix-like的操作系统中,fork()指的是一个系统调用,用于创建一个与当前进程几乎完全相同的新进程。新进程(子进程)是原进程(父进程)的副本,包括父进程的代码、数据、堆、栈等内容。子进程随后可以执行不同的程序(通过exec()系统调用)或继续执行与父进程相同的程序。

  • Java 中的 Fork (与 ForkJoinPool 和 ForkJoinTask 相关):

        在Java中,fork方法是ForkJoinTask类的一个方法,用于将当前任务拆分为子任务,并将这些子任务提交到ForkJoinPool中执行。这里的“fork”是为了利用多核处理器并行执行任务的机制,旨在提高性能。

总结:
        操作系统中的fork用于创建新的进程,该进程是当前进程的副本。
        Java中的fork(与ForkJoinPool和ForkJoinTask相关)用于将任务拆分为子任务,以便在并行环境中执行。

        可见,Java的fork与操作系统级别的fork在目的和用法上完全不同。Java的fork用于任务拆分和并行处理,而操作系统的fork用于创建新的进程(如在Redis中bgsave、rdb等实际都是通过系统调用fork一个子进程去执行一些耗时的操作,避免了主进程的阻塞执行)。

接下来,重点学习下java中的fork/join。

java中的fork/join,是自java7开始引入的一个框架,是用于并行编程的API,位于JUC(java.util.cocurrent)包中

fork:用于把任务拆分为子任务,并把这些子任务提交到ForkJoinPool中去执行;

join:等待任务执行完成,并获取其结果(如果有的话,分两类:RecursiveAction和RecursiveTask,后面会介绍)

到这一步,我们如果遇到一些开源项目中用到了fork/join,就可以简单理解为:fork-是把一个大任务拆分成多个子任务去执行了,join-是等待任务执行完成并获取最终结果,有点类似MapReduce的原理

接下来,再细展开说说。

java中的fork/join,通常与ForkJoinPool和ForkJoinTask类相关,在java7中被引入用于支持并行编程。

ForkJoinPool:

        ForkJoinPool 是一个可以执行 ForkJoinTask 的线程池。它使用工作窃取(work-stealing)算法来有效地利用线程,当一个线程完成其任务时,它会从其他线程的队列中窃取任务来执行。

        

        从类图中可以看出,ForkJoinPool继承自AbstractExecutorService,重写了线程池的一些方法(execute、submit等)。

ForkJoinTask:

        ForkJoinTask 是一个可以拆分为子任务的抽象任务。我们通常不直接继承它,它有两个主要的子类:RecursiveAction(用于没有返回结果的任务)和 RecursiveTask(用于有返回结果的任务)。

        

示例代码:

下面是一个简单的例子,通过使用ForkJoinPool和ForkJoinTask来计算一个大数组的总和。

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

public class ForkJoinSumCalculator extends RecursiveTask<Long> {

    private static final int THRESHOLD = 1000; // 阈值,当数组长度小于这个值时不再拆分
    private final long[] numbers;
    private final int start;
    private final int end;

    public ForkJoinSumCalculator(long[] numbers) {
        this(numbers, 0, numbers.length);
    }

    private ForkJoinSumCalculator(long[] numbers, int start, int end) {
        this.numbers = numbers;
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        int length = end - start;
        if (length <= THRESHOLD) {
            return computeDirectly();
        } else {
            int mid = (start + end) >>> 1; // 无符号右移,计算中点
            ForkJoinSumCalculator leftTask = new ForkJoinSumCalculator(numbers, start, mid);
            ForkJoinSumCalculator rightTask = new ForkJoinSumCalculator(numbers, mid, end);
            leftTask.fork(); // 拆分左侧任务
            Long rightResult = rightTask.compute(); // 直接计算右侧任务(因为compute会调用fork和join)
            Long leftResult = leftTask.join(); // 等待左侧任务完成并获取结果
            return leftResult + rightResult; // 合并结果
        }
    }

    private long computeDirectly() {
        long sum = 0;
        for (int i = start; i < end; i++) {
            sum += numbers[i];
        }
        return sum;
    }

    public static void main(String[] args) {
        long[] numbers = new long[1000000]; // 创建一个包含100万个元素的数组
        for (int i = 0; i < numbers.length; i++) {
            numbers[i] = i;
        }
        ForkJoinPool pool = new ForkJoinPool(); // 创建一个ForkJoinPool
        ForkJoinSumCalculator task = new ForkJoinSumCalculator(numbers); // 创建一个任务
        Long result = pool.invoke(task); // 提交任务并等待结果
        System.out.println("Sum: " + result); // 输出结果
    }
}

在这个例子中,ForkJoinSumCalculator 类继承自 RecursiveTask<Long>,它负责计算一个长整型数组的总和。当数组的长度超过某个阈值时,任务会被拆分(fork)为两个子任务,每个子任务计算数组的一部分的总和。然后,这些子任务的结果会被合并起来(join)得到最终的总和。

工作窃取(work-stealing)算法:

(让文心一言帮忙解释一下吧,非度厂员工,不是宣传,纯粹为了方便)

(不得不说,解释的还是很不错的,直观明了)

好了,就这样吧,大概够用了。

  • 33
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值