深入理解Java中的ForkJoin框架原理

在现代多核处理器的时代,有效地利用并行计算可以极大地提高程序的性能。Java中的ForkJoin框架是Java 7引入的一个并行计算框架,它提供了一种简单而高效的方式来利用多核处理器。在本文中,我们将深入探讨ForkJoin框架的原理和工作方式。

一、什么是ForkJoin框架?

ForkJoin框架是Java并发包(java.util.concurrent)的一部分,主要用于并行计算,特别适合处理可以递归划分成许多子任务的问题,例如大数据处理、并行排序等。该框架的核心思想是将一个大任务拆分成多个小任务(Fork),然后将这些小任务的结果汇总起来(Join),从而达到并行处理的效果。

二、ForkJoin框架的核心组件

1. ForkJoinPool

  • 这是执行ForkJoin任务的线程池。线程池中的每个线程都有一个自己的任务队列,当一个线程完成了它的任务后,它会尝试从其他线程的任务队列中“窃取”任务来执行(称为工作窃取(Work-Stealing)的算法)。这种机制有助于平衡负载,使得所有线程都能保持忙碌状态,从而提高 CPU 的利用率。

在工作窃取算法的实现过程中,ForkJoinPool会维护一个优先级队列(priority queue),用于存储等待被窃取的任务。每个工作线程都会维护着一个优先级队列,并使用优先级队列来实现工作窃取。当一个新任务到达时, ForkJoinPool会根据任务的优先级将任务分配给一个空闲的工作线程进行处理。如果所有的工作线程都忙碌或没有空闲状态,则该任务会被加入到优先级队列中等待处理。
需要注意的是,虽然工作窃取算法可以提高并行计算的效率,但它也可能带来一些负面影响。例如,如果某个工作线程一直处于忙碌状态而无法进行窃取操作,那么其他工作线程可能会因为缺乏任务而陷入等待状态,导致执行效率降低。因此,在使用工作窃取算法时需要根据具体情况进行调整和优化。
在这里插入图片描述

  • ForkJoinPool特别适合处理可以递归划分成许多子任务的问题,如大数据处理、并行排序等。开发者需要定义一个ForkJoinTask(通常是RecursiveAction或RecursiveTask的子类),并实现其compute方法,在该方法中描述任务的划分和执行逻辑。

  • 在应用场景方面,ForkJoinPool适合在有限的线程数下完成有父子关系的任务场景,比如快速排序、二分查找、矩阵乘法、线性时间选择等场景,以及数组和集合的运算。

总的来说,ForkJoinPool通过其特有的fork和join机制以及工作窃取算法,提供了一种简单而高效的方式来利用多核处理器进行并行计算

2. ForkJoinTask

  • ForkJoinTask通常是一个需要并行处理的任务,但它比传统的线程更轻量级。大量的任务和子任务可以存在于少量的实际线程中,因为ForkJoinTask使用了一种高效的线程管理策略。主ForkJoinTask被明确提交到ForkJoinPool后开始执行,或者没有参与ForkJoin计算,开始于ForkJoinPool.commonPool()。一旦启动,它通常会启动其它的子任务。

  • ForkJoinTask有两个重要的子类:RecursiveAction和RecursiveTask。RecursiveAction用于执行没有返回值的任务,而RecursiveTask用于执行有返回值的任务。这两个子类都需要实现一个compute()方法来定义任务的逻辑。

  • ForkJoinTask中的fork()方法用于将任务放入队列并安排异步执行,而join()方法则用于等待计算完成并返回计算结果。这些方法使得任务的分解和合并变得非常简单和高效。

三、 ForkJoin框架的工作原理

1. 任务划分(Fork):

  • 开发者需要定义一个ForkJoinTask(通常是RecursiveAction或RecursiveTask的子类),并实现其compute方法。
  • 在compute方法中,任务应该被检查是否可以进一步细分。如果可以,应该使用fork方法将其细分为子任务。
  • fork方法会异步地执行子任务,这意味着子任务会在ForkJoinPool中的一个线程上执行,而不会阻塞当前线程。

2. 任务执行和结果合并(Join):

  • 当一个任务无法再细分时,它应该开始执行其实际的工作。
  • 对于有返回值的任务(RecursiveTask),任务完成时需要返回其结果。
  • 其他任务可以使用join方法等待一个子任务完成,并获取其结果(仅适用于RecursiveTask)。
  • join方法是阻塞的,它会等待子任务完成。但是,由于ForkJoinPool使用了工作窃取算法,即使一个线程在等待子任务完成,它也可以执行其他任务,从而保持CPU的忙碌状态。

在这里插入图片描述

四、ForkJoin的使用

1. 先看下fork/join在stream中的应用:

Fork/Join框架在Java Stream API中有广泛的应用,尤其是在并行流(parallel streams)中。Stream API是Java 8引入的一种新的数据处理方式,它允许开发者以声明式的方式处理数据集合,如转换、过滤、映射、归约等操作。

当使用并行流时,Stream API会利用Fork/Join框架来并行处理数据。具体来说,Stream API会将大的数据集分割成多个小的数据块,然后利用Fork/Join框架的线程池来并行处理这些数据块。每个线程都会处理一个数据块,并将结果合并起来以得到最终的结果。

以下是一个简单的示例,展示了如何使用并行流和Fork/Join框架来计算一个大数组中所有元素的和:

import java.util.Arrays;  
import java.util.concurrent.ForkJoinPool;  
import java.util.stream.LongStream;  
  
public class ForkJoinStreamExample {  
    public static void main(String[] args) {  
        // 创建一个包含大量元素的长整型数组  
        long[] numbers = LongStream.rangeClosed(1, 1000000000L).toArray();  
  
        // 默认使用Fork/Join框架的并行流来计算数组元素的和  
        long sum = Arrays.stream(numbers).parallel().sum();  
  
        // 打印结果  
        System.out.println("Sum of all elements: " + sum);  
    }  
}

我们创建了一个包含大量元素的长整型数组,并使用Arrays.stream(numbers).parallel().sum()来计算数组中所有元素的和。这里,parallel()方法会将流转换为并行流,从而利用Fork/Join框架进行并行处理。sum()方法是一个归约操作,它会将流中的所有元素归约为一个单一的结果。

需要注意的是,虽然并行流可以显著提高处理大数据集的速度,但并不是所有情况下都应该使用它。如果数据集很小,或者每个元素的处理时间非常短,那么并行流可能会引入额外的开销,导致性能下降。因此,在使用并行流之前,最好先进行一些性能测试,以确定是否真正需要并行处理。

另外,值得注意的是,在Fork/Join框架中,任务的划分和合并是由框架自动处理的,而在Stream API中,开发者只需要指定要执行的操作,而不需要关心底层的并行处理细节。这使得使用Stream API进行并行处理变得更加简单和直观。

2. 再看自定义task任务的应用:

import java.util.concurrent.ForkJoinPool;  
import java.util.concurrent.RecursiveTask;  
  
// 继承 RecursiveTask,实现一个计算数组中元素和的任务  
public class SumArrayTask extends RecursiveTask<Integer> {  
  
    // 数组  
    private final int[] array;  
    // 计算的起始索引  
    private final int start;  
    // 计算的结束索引  
    private final int end;  
    // 阈值,当子数组的长度小于此值时,直接计算结果而不再拆分  
    private static final int THRESHOLD = 10;  
  
    // 构造函数  
    public SumArrayTask(int[] array, int start, int end) {  
        this.array = array;  
        this.start = start;  
        this.end = end;  
    }  
  
    // 核心的计算方法  
    @Override  
    protected Integer compute() {  
        // 计算子数组的长度  
        int length = end - start;  
          
        // 如果子数组长度小于阈值,则直接计算该子数组的和  
        if (length <= THRESHOLD) {  
            int sum = 0;  
            for (int i = start; i < end; i++) {  
                sum += array[i];  
            }  
            return sum; // 直接返回结果  
        } else {  
            // 如果子数组长度大于阈值,则拆分任务  
            int middle = start + length / 2; // 计算中点  
            SumArrayTask leftTask = new SumArrayTask(array, start, middle); // 创建左半部分子任务  
            SumArrayTask rightTask = new SumArrayTask(array, middle, end); // 创建右半部分子任务  
              
            // 异步执行左半部分子任务(fork),并等待右半部分子任务的结果(compute)  
            leftTask.fork(); // fork 是不阻塞的,它会将任务提交到 ForkJoinPool 中去异步执行  
            int rightResult = rightTask.compute(); // compute 是阻塞的,它会等待任务完成并返回结果  
              
            // 等待左半部分子任务的结果,并与右半部分子任务的结果合并  
            int leftResult = leftTask.join(); // join 会阻塞,直到任务完成  
            return leftResult + rightResult; // 合并结果并返回  
        }  
    }  
  
    // 主函数,用于测试  
    public static void main(String[] args) {  
        int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};  
          
        // 创建一个 ForkJoinPool,使用默认并行级别(通常等于处理器的核心数)  
        ForkJoinPool pool = new ForkJoinPool();  
          
        // 创建一个 SumArrayTask 任务来计算整个数组的和  
        SumArrayTask task = new SumArrayTask(array, 0, array.length);  
          
        // 提交任务到 ForkJoinPool 并获取结果  
        int sum = pool.invoke(task);  
          
        // 输出结果  
        System.out.println("The sum of the array elements is: " + sum);  
    }  
}

在这个示例中,我们创建了一个 SumArrayTask 类,它继承了 RecursiveTask。SumArrayTask 的任务是计算一个整数数组中指定范围内的元素之和。如果数组的范围小于一个给定的阈值(THRESHOLD),则直接计算;否则,任务会在中点处被拆分为两个子任务,然后递归地执行这些子任务。

compute 方法是任务的核心,它根据数组的长度来决定是直接计算结果还是拆分任务。fork 方法用于异步提交左子任务到 ForkJoinPool,而 compute 方法会阻塞等待右子任务的结果。一旦两个子任务都完成,它们的结果会通过 join 方法合并,并返回给调用者。

在 main 方法中,我们创建了一个 ForkJoinPool 实例和一个 SumArrayTask 实例,然后使用 pool.invoke(task) 方法来执行任务并获取最终结果。这个结果被打印到控制台上。

五、ForkJoin框架的优点

  • 自动并行化:通过简单地定义任务和递归地划分它们,开发者可以很容易地实现并行计算,而无需手动管理线程。
  • 工作窃取:ForkJoinPool的工作窃取算法可以自动平衡负载,确保所有处理器核心都得到充分利用。
  • 简单性:尽管其背后的原理可能很复杂,但使用ForkJoin框架的API相对简单,只需要实现少量的方法即可。

六、ForkJoin框架的局限性

  • 递归划分:ForkJoin框架最适合可以递归划分的问题。对于不适合递归划分的问题,使用ForkJoin可能不是最佳选择。
  • 任务开销:由于任务划分和结果合并的开销,对于非常小的任务,使用ForkJoin可能不如使用传统的单线程方法。
  • 异常处理:在ForkJoin框架中处理异常可能比较复杂,因为异常需要在任务链中传播。

七、总结一下

Java中的ForkJoin框架是一个强大而灵活的并行计算工具。通过递归地划分任务和自动地平衡负载,它可以帮助开发者充分利用现代多核处理器的性能。然而,像所有工具一样,了解它的工作原理和局限性是使用它的关键。在适合的场景下,ForkJoin框架可以是一个强大的性能优化工具。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/363596.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

《区块链简易速速上手小册》第10章:区块链的未来与趋势(2024 最新版)

文章目录 10.1 区块链的未来展望10.1.1 基础知识10.1.2 主要案例&#xff1a;区块链在金融领域的发展10.1.3 拓展案例 1&#xff1a;区块链在供应链管理中的应用10.1.4 拓展案例 2&#xff1a;区块链在身份管理和隐私保护中的应用 10.2 新兴技术与区块链的融合10.2.1 基础知识1…

数据结构之图

图 图&#xff08;Graph&#xff09;是比树还要难以理解和学习的“多对多”数据结构&#xff0c;可以认为树也是图的一种。图的知识点众多&#xff0c;按照存储路径的方向分&#xff0c;可分为无向图和有向图&#xff0c;按照图的存储结构分&#xff0c;可分为完全图与有向完全…

InstantID: Zero-shot Identity-Preserving Generation in Seconds

文章目录 IntroductionMainReference 记录由国内首创的一个好玩的小项目&#xff0c;图像生成领域的新进展。但我希望现阶段计算机视觉领域的研究能更聚焦在 语义分割 和 三维视觉 上&#xff0c;这样能更方便与机器人等产品和工业实体结合。 Introduction InstantID 是一个基…

phpstudy安装并运行redis

对于一个菜鸟来说&#xff0c;任何一个小步骤都可能研究半天&#xff0c;比如“phpstudy安装并运行redis”这一问题&#xff0c;解决好后第一时间记录下来&#xff0c;方便日后查看&#xff0c;也为遇到同样困难的小伙伴提供个参考&#xff01; 一、phpstudy安装redis 1.打开…

部署monggodb副本集分片集群

分片技术,可以满足MongoDB数据量大量增长的需求。当MongoDB存储海量的数据时&#xff0c;一台机器可能不足以存储数据&#xff0c;也可能不足以提供可接受的读写吞吐量。这时&#xff0c;我们就可以通过在多台机器上分割数据&#xff0c;使得数据库系统能存储和处理更多的数据。…

不废话的将ts一篇文章写完

写在前面 网上很多写ts的教程的&#xff0c;但是我觉得写的太繁琐了&#xff0c;这里我直接将基础用法写上&#xff0c;包括编译后的js代码&#xff0c;以便于你们进行对比&#xff0c; 包括一些常见的报错信息&#xff0c;你们可以对比一下报错信息&#xff0c; 我尽量不废话的…

图形化编程:Scratch与6547网题库的奇妙结合

随着科技的飞速发展&#xff0c;编程教育逐渐成为孩子们不可或缺的技能。其中&#xff0c;图形化编程因其简单易懂的特性&#xff0c;受到了广大儿童的喜爱。Scratch&#xff0c;这一由麻省理工学院开发的编程工具&#xff0c;正是引领这一风潮的佼佼者。与此同时&#xff0c;6…

LeetCode202. 快乐数

202. 快乐数 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为&#xff1a; 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字的平方和。然后重复这个过程直到这个数变为 1&#xff0c;也可能是 无限循环 但始终变不到 1。如果这个过程 结果为…

HTML5的新特性

目录 一&#xff0c;新增语义化标签 二&#xff0c;新增的多媒体标签 三&#xff0c;新增input表单 四&#xff0c;新增的表单属性 一&#xff0c;新增语义化标签 二&#xff0c;新增的多媒体标签 1&#xff0c;音频&#xff1a;<audio>.。。用MP3 <audio src…

JavaScript 基础五 对象

JavaScript 基础五 对象 1. 对象2. 对象使用① 声明语法② 对象有属性和方法组成③ 属性对象属性的增删改查操作 ④ 方法 3. 对象遍历实例 4. 内置对象① 内置对象② 内置对象Math属性方法 引入&#xff1a;保存网站用户信息&#xff0c;比如姓名、年龄、电话号码&#xff0c;用…

ENG-2,可用于监测细胞内钠离子的动态变化

Replacement of Asante NaTrium Green-2 AM钠离子指示探针&#xff0c;ENG-2&#xff0c;可用于监测细胞内钠离子的动态变化 您好&#xff0c;欢迎来到新研之家 文章关键词&#xff1a;Replacement of Asante NaTrium Green-2 AM钠离子指示探针&#xff0c;ENG-2 一、基本信…

如何对视频进行翻译

下载视频和翻译软件 视频和翻译软件点击下载就行了&#xff0c;下载之后解压&#xff0c;然后把两个exe点一下。接下来如果资金充裕或者要求比较高的可以使用各个api&#xff0c;网站里有视频介绍了。 经济适用视频翻译 原理简析 首先这个软件对视频的翻译的流程大致如下&a…

使用Python的Turtle模块简单绘制烟花效果

import turtle import random# 初始化屏幕 screen turtle.Screen() screen.bgcolor("black") screen.title("烟花模拟")# 创建一个Turtle来绘制烟花 firework turtle.Turtle() firework.hideturtle() firework.speed(0) # 设置绘图速度为最快# 绘制烟花…

如何系统的自学Python?通义千问、讯飞星火、文心一言及ChatGPT的回答

如何系统的自学Python&#xff1f;来看看通义千问、讯飞星火、文心一言及ChatGPT的回答. 第一个是马老师的通义千问 系统地自学Python是一个循序渐进的过程&#xff0c;从基础语法到实践项目&#xff0c;再到专业领域的深入学习。下面是一个详细的步骤指南&#xff1a; 了解Py…

vulhub靶机activemq环境下的CVE-2015-5254(ActiveMQ 反序列化漏洞)

影响范围 Apache ActiveMQ 5.x ~ Apache ActiveMQ 5.13.0 远程攻击者可以制作一个特殊的序列化 Java 消息服务 (JMS) ObjectMessage 对象&#xff0c;利用该漏洞执行任意代码。 漏洞搭建 没有特殊要求&#xff0c;请看 (3条消息) vulhub搭建方法_himobrinehacken的博客-CSD…

秦始皇帝陵K0007陪葬坑文物展览与文物预防性保护的璀璨交汇

秦始皇帝陵博物院近日迎来了一场引人注目的展览——“何止秦俑——秦陵苑囿之K0007陪葬坑”。此次展览首次集中展示了K0007陪葬坑出土的别具一格的陶俑、鲜活灵动的青铜水禽等珍贵文物。然而&#xff0c;这些文物的安全展出离不开高科技的监测平台与实时终端的24小时不间断保护…

英语不太行?数模美赛必备的翻译工具!

DeepL翻译:全世界最准确的翻译&#xff08;自称&#xff09; 网址&#xff1a;https://www.deepl.com/translator 优点&#xff1a;在专有名词翻译方面很准确&#xff0c;适合学术论文,可免费全文件翻译 缺点&#xff1a;全文件翻译时格式较乱&#xff0c;不过可以用于帮助初步…

Qt第一个项目(元对象系统)

效果是这样的&#xff0c;点击boy长大一岁或者girl长大一岁 qt的文件构造都是两个头文件三个源文件&#xff0c;源文件中有个cpp文件&#xff0c;它是程序的入口&#xff0c;一个项目中只能有一个main函数 #include "widget.h"#include <QApplication>int mai…

C#用正则表达式Regex.Matches 方法检查字符串中重复出现的词

目录 一、Regex.Matches 方法 1.重载 二、Matches(String, String, RegexOptions, TimeSpan) 1.定义 2.示例 三、Matches(String, String, RegexOptions) 1.定义 2.示例 3.示例&#xff1a;用正则表达式检查字符串中重复出现的词 四、Matches(String, Int32) 1.定义…

DRV8301 踩坑记,Status1 D10 老是 Fault

波形如上&#xff1a; 看第一个时钟出来的数据&#xff08;Status1 读完自动清除&#xff1f;&#xff09;&#xff0c;因此数据是&#xff1a;0x20 输入结构体解析&#xff1a; 可以看到&#xff0c;FETHA_OC了也就是A桥上管过流了&#xff1b; 检查一下硬件看看&#xff1…