Java 并发编程 —— Fork/Join 框架的原理详解

目录

一. 前言

二. 并发和并行

2.1. 并发

2.2. 并行

2.3. 分治法

三. ForkJoin 并行处理框架的理论

3.1. ForkJoin 框架概述

3.2. ForkJoin 框架原理

3.3. 工作窃取算法

四. ForkJoin 并行处理框架的实现

4.1. ForkJoinPool 类

4.2. ForkJoinWorkerThread 类

4.3. ForkJoinTask 类

4.4. ForkJoin 示例

五. 总结


一. 前言

    在 JDK 中,提供了这样一种功能:它能够将复杂的逻辑拆分成一个个简单的逻辑来并行执行,待每个并行执行的逻辑执行完成后,再将各个结果进行汇总,得出最终的结果数据。有点像Hadoop 中的 MapReduce。

    ForkJoin 是由 JDK1.7 之后提供的多线程并发处理框架。ForkJoin 框架的基本思想是分而治之。什么是分而治之?分而治之就是将一个复杂的计算,按照设定的阈值分解成多个计算,然后将各个计算结果进行汇总。相应的,ForkJoin 将复杂的计算当做一个任务,而分解的多个计算则是当做一个个子任务来并行执行。

二. 并发和并行

并发和并行在本质上还是有所区别的。

2.1. 并发

    并发指的是在同一时刻,只有一个线程能够获取到 CPU 执行任务,而多个线程被快速的轮换执行,这就使得在宏观上具有多个线程同时执行的效果,并发不是真正的同时执行,并发可以使用下图表示:

2.2. 并行

    并行指的是无论何时,多个线程都是在多个 CPU 核心上同时执行的,是真正的同时执行。

2.3. 分治法

    把一个规模大的问题划分为规模较小的子问题,然后分而治之,最后合并子问题的解得到原问题的解。在分治法中,子问题一般是相互独立的,因此,经常通过递归调用算法来求解子问题。

    步骤可分为:1. 分割原问题;2. 求解子问题;3. 合并子问题的解为原问题的解。我们可以使用如下伪代码来表示这个步骤:

if (任务很小){
    直接计算得到结果
} else {
    分拆成N个子任务
    调用子任务的fork()进行计算
    调用子任务的join()合并计算结果
}

典型应用有:二分搜索、大整数乘法、Strassen矩阵乘法、棋盘覆盖、合并排序、快速排序、线性时间选择、汉诺塔。

三. ForkJoin 并行处理框架的理论

3.1. ForkJoin 框架概述

    Java 1.7 引入了一种新的并发框架 —— Fork/Join Framework,主要用于实现“分而治之”的算法,特别是分治之后递归调用的函数。

    ForkJoin 框架的本质是一个用于并行执行任务的框架,能够把一个大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务的计算结果。在 Java 中,ForkJoin 框架与 ThreadPool 共存,并不是要替换 ThreadPool。

    其实,在 Java 8 中引入的并行流计算,内部就是采用的 ForkJoinPool 来实现的。例如,下面使用并行流实现打印数组元组的程序:

public class SumArray {
    public static void main(String[] args) {
        List<Integer> numberList = Arrays.asList(1,2,3,4,5,6,7,8,9);
        numberList.parallelStream().forEach(System.out::println);
    }
}

说到这里,可能有读者会问:可以使用线程池的 ThreadPoolExecutor 来实现啊?为什么要使用ForkJoinPool? 接下来,我们就来回答这个问题。

3.2. ForkJoin 框架原理

    ForkJoin 框架是从 JDK1.7 中引入的新特性,它同 ThreadPoolExecutor 一样,也实现了Executor 和 ExecutorService 接口。它使用了一个无限队列来保存需要执行的任务,而线程的数量则是通过构造函数传入,如果没有向构造函数中传入指定的线程数量,那么当前计算机可用的 CPU 数量会被设置为线程数量作为默认值。

    ForkJoinPool 主要使用分治法(Divide-and-Conquer Algorithm)来解决问题。典型的应用比如快速排序算法。这里的要点在于,ForkJoinPool 能够使用相对较少的线程来处理大量的任务。

    比如要对1000万个数据进行排序,那么会将这个任务分割成两个500万的排序任务和一个针对这两组500万数据的合并任务。以此类推,对于500万的数据也会做出同样的分割处理,到最后会设置一个阈值来规定当数据规模到多少时,停止这样的分割处理。

    比如,当元素的数量小于10时,会停止分割,转而使用插入排序对它们进行排序。那么到最后,所有的任务加起来会有大概200万+个。问题的关键在于,对于一个任务而言,只有当它所有的子任务完成之后,它才能够被执行。

    所以当使用 ThreadPoolExecutor 时,使用分治法会存在问题,因为 ThreadPoolExecutor 中的线程无法向任务队列中再添加一个任务并在等待该任务完成之后再继续执行。而使用 ForkJoinPool就能够解决这个问题,它就能够让其中的线程创建新的任务,并挂起当前的任务,此时线程就能够从队列中选择子任务执行。

那么使用 ThreadPoolExecutor 或者 ForkJoinPool,性能上会有什么差异呢?

    首先,使用 ForkJoinPool 能够使用数量有限的线程来完成非常多的具有父子关系的任务,比如使用4个线程来完成超过200万个任务。但是,使用 ThreadPoolExecutor 时,是不可能完成的,因为 ThreadPoolExecutor 中的 Thread 无法选择优先执行子任务,需要完成200万个具有父子关系的任务时,也需要200万个线程,很显然这是不可行的,也是很不合理的!

3.3. 工作窃取算法

    假如我们需要做一个比较大的任务,我们可以把这个任务分割为若干互不依赖的子任务,为了减少线程间的竞争,于是把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应,比如 A 线程负责处理 A 队列里的任务。但是有的线程会先把自己队列里的任务干完,而其他线程对应的队列里还有任务等待处理。干完活的线程与其等着,不如去帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行。

工作窃取算法的优点:
充分利用线程进行并行计算,并减少了线程间的竞争。

工作窃取算法的缺点:
在某些情况下还是存在竞争,比如双端队列里只有一个任务时。并且该算法会消耗更多的系统资源,比如创建多个线程和多个双端队列。

四. ForkJoin 并行处理框架的实现

ForkJoin 框架中一些重要的类如下所示:

Fork/Join 框架类图

4.1. ForkJoinPool 类

    既然任务是被逐渐的细化的,那就需要把这些任务存在一个池子里面,这个池子就是ForkJoinPool,它与其它的 ExecutorService 区别主要在于它使用“工作窃取”。由类图可以看出,ForkJoinPool 类实现了线程池的 Executor接口。我们还可以使用 Executors.newWorkStealPool() 方法来创建 ForkJoinPool。

ForkJoinPool 中提供了如下提交任务的方法:

public void execute(ForkJoinTask<?> task)
public void execute(Runnable task)
public <T> T invoke(ForkJoinTask<T> task)
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) 
public <T> ForkJoinTask<T> submit(ForkJoinTask<T> task)
public <T> ForkJoinTask<T> submit(Callable<T> task)
public <T> ForkJoinTask<T> submit(Runnable task, T result)
public ForkJoinTask<?> submit(Runnable task)

4.2. ForkJoinWorkerThread 类

实现 ForkJoin 框架中的线程。

4.3. ForkJoinTask<V> 类

    ForkJoinTask 封装了数据及其相应的计算,并且支持细粒度的数据并行。ForkJoinTask 比线程要轻量,ForkJoinPool 中少量工作线程能够运行大量的 ForkJoinTask。ForkJoinTask 类中主要包括两个方法 fork() 和 join(),分别实现任务的分拆与合并。

    fork() 方法类似于 Thread.start(),但是它并不立即执行任务,而是将任务放入工作队列中。跟Thread.join() 方法不同,ForkJoinTask 的 join() 方法并不简单的阻塞线程,而是利用工作线程运行其他任务,当一个工作线程中调用 join(),它将处理其他任务,直到注意到目标子任务已经完成。

我们可以使用下图来表示这个过程:

ForkJoinTask有3个子类:

  1. RecursiveAction:无返回值的ForkJoinTask,并实现 Runnable。
  2. RecursiveTask:有返回值的ForkJoinTask,并实现 Callable。
  3. CountedCompleter:完成任务后将触发其他任务。

4.4. ForkJoin 示例

package com.lm.concurrency.example;

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

public class ForkJoinTaskExample extends RecursiveTask<Integer> {
    public static final int threshold = 2;

    private int start;
    private int end;

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

    @Override
    protected Integer compute() {
        int sum = 0;
        // 如果任务足够小就计算任务
        boolean canCompute = (end - start) <= threshold;
        if (canCompute) {
            for (int i = start; i <= end; i++) {
                sum += i;
            }
        } else {
            // 如果任务大于阈值,就分裂成两个子任务计算
            int middle = (start + end) / 2;
            ForkJoinTaskExample leftTask = new ForkJoinTaskExample(start, middle);
            ForkJoinTaskExample rightTask = new ForkJoinTaskExample(middle + 1, end);
 
            // 执行子任务
            leftTask.fork();
            rightTask.fork();
 
            // 等待任务执行结束合并其结果
            int leftResult = leftTask.join();
            int rightResult = rightTask.join();
 
            // 合并子任务
            sum = leftResult + rightResult;
        }
        return sum;
    }

    public static void main(String[] args) {
        ForkJoinPool forkjoinPool = new ForkJoinPool();
 
        // 生成一个计算任务,计算1+2+3+4
        ForkJoinTaskExample task = new ForkJoinTaskExample(1, 100);
 
        // 执行一个任务
        Future<Integer> result = forkjoinPool.submit(task);
 
        try {
            System.out.println("result: " + result.get());
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

五. 总结

Fork/Join 框架局限性:

对于 Fork/Join 框架而言,当一个任务正在等待它使用 Join 操作创建的子任务结束时,执行这个任务的工作线程查找其他未被执行的任务,并开始执行这些未被执行的任务,通过这种方式,线程充分利用它们的运行时间来提高应用程序的性能。为了实现这个目标,Fork/Join 框架执行的任务有一些局限性。

  1. 任务只能使用 Fork 和 Join 操作来进行同步机制,如果使用了其他同步机制,则在同步操作时,工作线程就不能执行其他任务了。比如,在 Fork/Join 框架中,使任务进行了睡眠,那么,在睡眠期间内,正在执行这个任务的工作线程将不会执行其他任务了。
  2. 在 Fork/Join 框架中,所拆分的任务不应该去执行 IO 操作,比如:读写数据文件。
  3. 任务不能抛出检查异常,必须通过必要的代码来处理这些异常。

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

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

相关文章

大华 DSS 城市安防数字监控系统 SQL 注入漏洞

漏洞简介 大华DSS数字监控系统itcBulletin接口对传入的数据没有预编译和充足的校验&#xff0c;导致该接口存在SQL注入漏洞&#xff0c;可通过注入漏洞获取数据库敏感信息。 资产测绘 app“dahua-DSS” 漏洞复现 POC: POST /portal/services/itcBulletin?wsdl HTTP/1.1 H…

图卷积神经网络发展

1. 图神经网络&#xff08;GNN&#xff09; 图神经网络的概念最早在2005年提出。2009年Franco博士在其论文 [2]中定义了图神经网络的理论基础。 本文中所提到的图均指图论中的图(Graph)。它是一种由若干个结点(Node)及连接两个结点的边(Edge)所构成的图形&#xff0c;用于刻画…

从源码到实践:深入了解鸿鹄电子招投标系统与电子招投标

在数字化采购领域&#xff0c;企业需要一个高效、透明和规范的管理系统。通过采用Spring Cloud、Spring Boot2、Mybatis等先进技术&#xff0c;我们打造了全过程数字化采购管理平台。该平台具备内外协同的能力&#xff0c;通过待办消息、招标公告、中标公告和信息发布等功能模块…

eslint严格规则检测问题报错

由于eslint严格规则检测问题报错时&#xff0c;更改两个文件package.json和vue.config.js package.json "no-unused-components": "off" vue.config.js lintOnSave: false 更改完成之后&#xff0c;重新run一下就OK了

DC电源模块在工业自动化中的关键应用案例分析

BOSHIDA DC电源模块在工业自动化中的关键应用案例分析 DC电源模块在工业自动化中有许多关键应用案例&#xff0c;以下是其中的一些&#xff1a; 1. 电机控制系统&#xff1a;在工业自动化中&#xff0c;电机控制是非常常见的应用。DC电源模块用于为电机提供稳定的直流电源&…

25考研指导规划建议(内含宝典级资料)

句句肺腑&#x1f4aa; &#x1f4cc;1.没有导师不喜欢要英语好的学生。四六级报名咨询查询链接 普通大学生至少要拿到四级证书四级真题和资料&#xff0c;有解析包括听力 &#x1f4cc;2.政治开始越早&#xff0c;离上岸越远 &#x1f4cc;3.每年7、8月是弃考高峰、11月再弃…

聪明高效能力广,AGI如何赋能内容管理?

文 | 智能相对论 作者 | 叶远风 毫无疑问&#xff0c;现在的大模型在技术比拼之外&#xff0c;如何通过产品化的方式走入到实际业务&#xff0c;是各厂商的着力点。 而一些一贯与数字化场景紧密融合的服务厂商&#xff0c;在大模型浪潮一开始就已经走在落地一线。 大数据基…

mysql使用全文索引+ngram全文解析器进行全文检索

表结构&#xff1a;表名 gamedb 主键 id 问题类型 type 问题 issue 答案 answer 需求 现在有个游戏资料库储存在mysql中&#xff0c;客户端进行搜索&#xff0c;需要对三个字段进行匹配&#xff0c;得到三个字段的相关性&#xff0c;选出三个字段中相关性最大的值进…

基于alibaba druid的血缘解析工具

基于alibaba druid的血缘解析 1、前言 仅仅对mysql数据库的select查询语句进行了血缘解析&#xff0c;该血缘解析包含了原始表字段、临时表字段和目标表字段的关联关系。 2、涉及到技术 主要使用了druid的如下接口对语法树进行解析&#xff1a; &#xff08;1&#xff09;…

Linux笔记---系统信息

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;Linux学习 ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 命令 1. uname - 显示系统信息 2. hostname - 显示或设置系统主机名 3. top - 显示系统资源使用情况 4. df - 显示磁盘空间使用情…

HTTP协议 -JavaWeb基础必知

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 本…

2010-2020年中国1km陆地生态系统碳汇

2010-2020年中国1km陆地生态系统碳汇数据 引用地址&#xff1a; 赵俊芳.2010-2020年中国1km陆地生态系统碳汇产品数据集,北京:中国科学院地理科学与资源研究所,2023.doi:10.12237/casearth.6538842e819aec0f26f42cb0 数据作者 作者&#xff1a;赵俊芳 版权机构&#xff1a;中…

YoloV8的一个缓存问题

摘要 如果尝使用YoloV8&#xff0c;我们一定遇到这个问题&#xff1a;明明都配置正确了&#xff0c;但是还是报错&#xff0c;数据的类别一直匹配不上&#xff0c;数据集指向上一个YoloV8的数据集&#xff0c;这时候你就要检查一下缓存了&#xff01; 解决方法 如果是Win电脑…

ChatGPT助力Excel数据分析:让你的工作事半功倍!

文章目录 一、ChatGPT简介二、ChatGPT在Excel数据分析中的应用1. 数据清洗2. 数据处理3. 数据分析4. 数据可视化 三、如何使用ChatGPT进行Excel数据分析1. 安装ChatGPT插件2. 输入问题或命令3. 查看结果并调整参数4. 导出结果并分享四、总结与展望 《巧用ChatGPT高效搞定Excel数…

Keil编译STM32工程,提示__align(4)处语法错误

好久没有用Keil编程&#xff0c;因为别人的代码是用Keil写的&#xff0c;所以又得安装起来&#xff0c;编译时遇到__align(4)的错误提示。 这个问题主要是编译器版本的问题&#xff0c;默认使用的是v6.19版本的编译器&#xff0c;而工程原来使用的是v5版本的&#xff0c;两个编…

Android: Ubuntu下交叉环境编译常用调试工具demo for lspci命令(ARM设备)

lspci命令交叉环境编译(ARM设备) 交叉编译工具下载&#xff1a; https://releases.linaro.org/components/toolchain/binaries https://releases.linaro.org/components/toolchain/binaries/6.3-2017.05/aarch64-linux-gnu/ lspci命令交叉环境编译(ARM设备)&#xff1a; 1&a…

本地文件内容搜索神器AnyTXT Searcher如何搭建与远程访问

文章目录 前言1. AnyTXT Searcher1.1 下载安装AnyTXT Searcher 2. 下载安装注册cpolar3. AnyTXT Searcher设置和操作3.1 AnyTXT结合cpolar—公网访问搜索神器3.2 公网访问测试 4. 固定连接公网地址 前言 你是否遇到过这种情况&#xff0c;异地办公或者不在公司&#xff0c;想找…

爬虫实战案例 -- 爬取豆瓣读书网页内容

进入网站检查信息 , 确定请求方式以及相关数据 找到爬取目标位置 开始敲代码 # 链接网站 def url_link(url):res requests.get(url,headers headers)response res.textparse_data(response)# 爬取信息 def parse_data(data):msg <li\sclass"media\sclearfix…

从安全性角度,看“可信数字底座”有何价值

文章目录 每日一句正能量前言概念对比安全技术对比思考与建议 每日一句正能量 不管现在有多么艰辛&#xff0c;我们也要做个生活的舞者。 前言 万向区块链此前提出“可信数字底座”这一概念和技术&#xff0c;即将区块链与物联网、人工智能、隐私计算等数字化技术相融合&#…

Java开发手册

扫一扫二维码关注公众号 1、每日推送人工智能、数据科学等行业最新重磅报告&#xff1b;2、回复“干货” &#xff0c;获取海量个性化推荐相关技术文档&#xff1b;3、最大最全的个性化推荐技术与产品社区&#xff0c;欢迎来撩&#xff1b; 个性化推荐技术与产品社区 前 言 …