JUC(5) : ForkJoinPool | 线程的极致管理

news2025/1/12 22:59:06

一、前言

前文介绍了线程的异步编排工具类 CompletableFuture 的使用,使用它能够很好的完成线程任务的编排工作,但同时,我们也注意到,其使用的默认线程池是 ForkJoinPool.commonPool() 的方法。则这个线程池是共用的,而为了业务之间互不影响,比如 A 业务秒杀并发量大,占用了大多数的线程,那 B 业务再使用这个线程池的话,就无法很好的推进下去。

观其源码也知在并行流时用到了 ForkJoinPool 的公共线程池。ForkJoinPool 是专门设计用于 Fork/Join 的线程池,什么是 Fork/Join 呢?这个 ForkJoinPool 这个线程池和我们学的 ThreadPoolExcutor 有啥区别呢?下图为ForkJoinPool 与我们熟知的线程池间的继承关系。下文,我们将解答这些疑惑,并学习相关概念并上手使用。

二、ForkJoinPool

2.1 概述

首先,查看该类介绍,该类是 Doug Lea 在JDK1.7版本中添加的新的线程池。Fork为分叉,Join 为连接,而其设计的思想也确实是先分开处理后合并处理的工作原理。核心思想就是把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务的结果。

Fork:把一个复杂任务进行分拆,大事化小
Join:把分拆任务的结果进行合并
复制代码

这个思想就很像 Hadoop 中 MapReduce 的工作原理,都是先组装成各个 Map ,然后在 Reduce 中完成合并。 ForkJoinPool 也是使用了分而治之的算法,用相对较少的线程处理大量的任务,将大任务一拆为二,以此类推,每个子任务再分成两半,知道达到阈值(自己设定)然后组成 N 个具有父子层级关系的任务。这时候 ForkJoinPool 就会安排合理的工作线程,例如 8个线程,让它们不断得去干活,先把它们的最下层的小任务干完,接着再去干小任务的父任务,一层层直到任务结束。

由此,可知既然它擅长干一些能拆分成父子任务的工作。所以例如快速排序、二叉查找等任务。最适合的是计算密集型的任务,比如是数据量很大的一个统计,如果是存在 IO交互,线程间同步,那就不太适合了。

2.2 累加案例

同样是线程池,为什么还要加个 ForkJoinPool 呢?你与我们老牌线程池有什么区别?就考虑一个场景,如果要计算 1-10000000 的复杂任务计算,如果使用一个原有的线程池 ThreadPool ,它只能有一个线程上场,而线程池里的其他线程就在那干瞪眼,这时候,为了进一步解决此类场景的问题,ForkJoinPool 提出了,下面我们通过一个计算案例简单了解下其是如何使用的。

public class ForkJoinPoolDemo {
    public static void main(String[] args) {
        singleDeal(1,100000000L);
        System.out.println("==============");
        ForkJoinDeal(1,100000000L);
    }
​
    private static void singleDeal(int start,long end) {
        long startTime = System.currentTimeMillis();
        long sum=0;
        for (int i = start; i <= end; i++) {
            // 累加
            sum +=i;
        }
        System.out.println(sum);
        System.out.println("单线程消耗的时间:"+(System.currentTimeMillis() - startTime));
    }
​
    private static void ForkJoinDeal(int start,long end) {
        long startTime = System.currentTimeMillis();
        //定义任务
        TaskExample taskExample = new TaskExample(start, end);
        //定义执行对象
        ForkJoinPool forkJoinPool = new ForkJoinPool(4);
        //加入任务执行
        ForkJoinTask<Long> result = forkJoinPool.submit(taskExample);
        //输出结果
        try {
            System.out.println(result.get());
        }catch (Exception e){
            e.printStackTrace(
        );
        }finally {
            forkJoinPool.shutdown();
            System.out.println("ForkJoin 消耗的时间:"+(System.currentTimeMillis() - startTime));
        }
    }
}
复制代码
@Slf4j
public class TaskExample extends RecursiveTask<Long> {
    private long start;
    private long end;
    private long sum;
    /**
     * 构造函数
     * @param start
     * @param end
     */
    public TaskExample(long start, long end){
        this.start = start;
        this.end = end;
    }
    @Override
    protected Long compute() {
        // 大于 100 个数相加切分,小于直接加
        if(end -start <10000){
            for (long i = start; i <= end; i++) {
                // 累加
                sum +=i;
            }
        }else{
            // 切分为 2 块
            long middle = (start +end)/2;
            // 递归调用,切分为 2 个小任务
            TaskExample taskExample = new TaskExample(start, middle);
            TaskExample taskExample2 = new TaskExample(middle+1,end);
            // 执行:异步
            taskExample.fork();
            taskExample2.fork();
            // 同步阻塞获取执行结果
            sum = taskExample.join() + taskExample2.join();
        }
        return sum;
    }
}
复制代码

观察上述代码,使用 4个线程,去计算任务,将一个大的计算任务拆分为 0—10000 、10000-20000依次类推,然后最终计算结果被依次合并,得到最终的结果。

也看到 ForkJoin 比使用单线程计算会慢很多,但如果改变任务的切分粒度,比如提到10w 一个计算任务,那效率就会提示一些,所以,ForkJoin 在面对一些复杂的计算型任务时可以考虑,一般情况下也用不上。

2.3 ThreadPool 与 ForkJoinPool

事实上,它更多的是原有线程池的一个补充,在特定的场景下,使用它会更合适。下面对两者进行一个简单对比:

  • 应用场景:

    • ThreadPool :常用于线程并发,阻塞延时较长的任务,这张任务一般要求线程个数较多。
    • ForkJoinPool:用于大任务可分解成小任务的情况下,一般是处理计算密集型任务。
  • 基本原理对比:

    • ThreadPool:所有线程共用一个队列,然后这些线程排着队去干活,来一个任务,派一个线程去,避免了线程的创建和销毁的开销。
    • ForkJoinPool: 每个线程都是一个队列,可以用极少的线程干非常多的父子任务,然后将每个任务的结果合并进而得出最终结果。

工作窃取机制

ForkJoinPool 提供了一个有效利用线程池机制的方法,就是当一个任务执行完成后,就是自动从队列尾部获取新的任务去执行,这样在任务量很大的时候,CPU 多的计算机会表现出很好的性能。

如图中的 A、B线程,分别从队列中读取任务,然后放入(push)进自己的队列中,同时也取出(pop)自己队列的任务进行消费,那为什么又放又取呢?不直接取最右边的任务?这主要是设计者为了CPU缓存的命中率,然后B 线程空闲时,可以偷取(poll) A 线程的未执行的任务。提高 线程工作效率。

三个API 方法:

方法名说明
invoke(ForkJoinTask)提交任务并一直阻塞直到任务执行完成返回合并结果
execute(ForkJoinTask)异步执行任务,无返回值。
submit(ForkJoinTask)异步执行任务,返回task本身,可以通过task.get()方法获取合并之后的结果。

2.4 ForkJoinTask

上面的计算案例中,我们创建出一个类,继承了 RecursiveTask ,然后交给 ForkJoinPool 提交任务。因为,ForkJoinPool 只会处理 ForkJoinTask 的任务。而我们用的 RecursiveTask 是其重要的实现类

我们要使用 Fork/Join 框架,首先需要创建一个 ForkJoin 任务。该类提供了在任务中执行 fork 和 join 的机制。通常情况下我们不需要直接集成 ForkJoinTask 类,只需要继承它的子类,Fork/Join 框架提供了几个子类:

  • RecursiveAction:用于没有返回结果的任务

  • RecursiveTask : 用于有返回结果的任务

  • CountedCompleter : 无返回结果,完成任务后可以触发回调

FrokJoinTask 提供了两个重要的方法:

  • fork:让 task 异步执行
  • join:让 task 同步执行,可以获取返回值

ForkJoinPool 由 ForkJoinTask 数组和 ForkJoinWorkerThread 数组组成,ForkJoinTask 数组负责将存放以及将程序提交给 ForkJoinPool,而 ForkJoinWorkerThread 负责执行这些任务。

三、小结

对于 ForkJoinPool 相关的知识点需要记住以下几点:

  • CompletableFuture 在使用并行流计算的时候会调用 ForkJoinPool 的commonPool 方法,但这个方法可能会被很多任务共同使用,所以要谨慎使用,使用自己创建的线程池。
  • ForkJoinPool 是针对计算密集型的任务比较适合,如果数据量不大,没有使用 ForkJoinPool的必要,单线程也许更快。
  • ForkJoinPool 的执行效率与任务分片的粒度和线程数和数据量都有关联,需要仔细评估使用

分类:

后端

标签:

后端Java面试

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一个普通前端的2022年终总结:多病的一年

多病 用一个词总结我的2022 &#xff0c;毫无疑问是【多病】。 翻看挂号记录&#xff0c;今年累计跑了19次医院&#xff0c;除去定期的脱发复查、尿常规复查外&#xff0c;其他还得了皮肤病、急性咽炎、筋膜炎、结膜炎、肾结石、慢性胃炎、胸闷&#xff0c;体验过了无法忍受的…

基于java+springmvc+mybatis+jsp+mysql的网络作者与美工交流平台

项目介绍 本次设计任务是要设计一个网络作者与美工交流平台&#xff0c;通过这个系统能够满足网络作者与美工交流信息的管理及版主的网络作者与美工交流信息管理功能。系统的主要功能包括&#xff1a;主页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;版主管理&#…

text文本属性

text文本属性 源代码 color color属性用于定义文本的颜色&#xff0c;有预定义的颜色值(red, blue, yellow)、十六进制(#FF0000, #FF6600,#29D794)、RBG代码(rgb(255,0,0)或rgb(100%,0%,0%)) text-align text-align属性用于设置元素内文本的水平对…

R语言对BRFSS数据探索回归数据分析

执行摘要 最近我们被客户要求撰写关于BRFSS的研究报告&#xff0c;包括一些图形和统计输出。该项目包括探索一个现实世界的数据集-CDC的2013年 行为风险因素监视系统 -并针对三个 选择的研究问题创建报告。 选择的研究问题及其各自的结果是&#xff1a; 被访者对其健康状况…

Redis框架(一):Redis入门和Jedis连接池

Redis入门和Jedis连接池&#xff1a;基本介绍实例Demo源码分析SpringCloud章节复习已经过去&#xff0c;新的章节Redis开始了&#xff0c;这个章节中将会回顾Redis 主要依照以下几个原则 基础实战的Demo和Coding上传到我的代码仓库在原有基础上加入一些设计模式&#xff0c;st…

c#扩展方法

1、前言: 通常,我们想要向一个类型中添加方法,可以通过以下两种方式: 修改源代码。 在派生类中定义新的方法。 但是以上方法并不是万能的,我们并不能保证拥有一个类型的源码,也并不能保证这个类型可以让我们继承(如结构,枚举,String等等)。但是C#提供了一个办法,…

教你如何写一个符合自己需求的小程序日历组件

1|0 前言 很多时候&#xff0c;我们生活中会有各种打卡的情况&#xff0c;比如 keep 的运动打卡、单词的学习打卡和各种签到打卡或者酒店的入住时间选择&#xff0c;这时候就需要我们书写一个日历组件来处理我们这种需求。 但是更多时候&#xff0c;我们都是网上找一个插件直…

【HBase】【一】windows搭建源码开发环境

目录环境配置1. Windows安装Cygwin2. 安装ProtocolBuffers3. 启动zookeeper4. 搭建Hadoop环境5. 编译Hbase源码6. 启动HRegionServer7. 启动HMaster8. 启动HShell客户端环境配置 系统&#xff1a;windows10 IDE: Eclipse hadoop: 3.3.4 hbase: 2.4.15 java: 17 1. Window…

pytest学习——pytest插件的7种用法

1.pytest-repeat 重复跑 安装包 pip install pytest-repeat第一种用法&#xff1a; 装饰器 pytest.mark.repeat(次数) 示例代码 import pytest pytest.mark.repeat(5) def test_001(): assert 12 if __name__ __main__: pytest.main([-sv,__file__])第二种用法&#xff1a…

[附源码]Python计算机毕业设计SSM基于数据挖掘的毕业生离校信息系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 Ma…

基于牛顿方法在直流微电网潮流研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f389;作者研究&#xff1a;&#x1f3c5;&#x1f3c5;&#x1f3c5;本科计算机专业&#xff0c;研究生电气学硕…

拆解理想汽车Q3财报:收入增速继续下滑,年内两次更换首席技术官

12月9日&#xff0c;理想汽车&#xff08;NASDAQ:LI、HK:02015&#xff09;发布截至2022年9月30日止季度&#xff08;即2022年第三季度&#xff09;的未经审计财务业绩。财报显示&#xff0c;理想汽车2022年第三季度的收入为93.42亿元&#xff0c;同比增加20.2%&#xff0c;低于…

(九)Vue之侦听/监听/监视属性

文章目录普通实现监视属性实现Vue里配置监视属性Vue外配置监视属性配置属性immediate配置deep&#xff08;深度监视&#xff09;配置普通监视监视多级结构中某个属性的变化监视多级结构中所有属性的变化监视属性简写watch配置简写$watch配置简写监视属性vs计算属性Vue学习目录上…

2023最新SSM计算机毕业设计选题大全(附源码+LW)之java农产品推广平台98966

对于计算机专业的学生最头疼的就是临近毕业时的毕业设计,对于如何选题,技术选型等这些问题,难道了大部分人,确实,还没毕业的学生对于这些问题还比较陌生,只有学习的理论知识,没有实战经验怎么能独自完成毕业设计这一系列的流程,今天我们就聊聊如何快速应对这一难题. 比较容易的…

ITK 形态学中的开运算和闭运算 腐蚀 膨胀

一. 图像形态学处理 —— 膨胀和腐蚀 腐蚀在二值图像的基础上做“收缩”或“细化”操作; 膨胀在二值图像的基础上做“加长”或“变粗”的操作。 什么是二值图像呢&#xff1f;把一幅图片看做成一个二维的数组&#xff0c;那么二值图像是一个只有0和1的逻辑数组&#xff0c;我们…

vertical-align属性

vertical-align属性 CSS的vertical-align属性使用场景&#xff0c;经常用于设置图片或者表单(行内块元素)和文字垂直对齐 用于设置一个元素的垂直对齐方式&#xff0c;但是它只针对于行内元素或者行内块元素有效 源代码 语法&#xff1a; vertical-align { baseline | top | …

序——在linux下学习C语言

目录 在Linux下学习C语言的前提。。。 一、Linux的一些常见命令 二、Linux中VI和VIM的一些命令操作 1、在VIM中控制光标 2、vim中的插入模式 3、退出插入模式的方法 4、在VIM模式中的删除命令 5、撤销命令 6、 粘贴和拷贝命令 7、查看文件信息和寻找另一半括号 8、缩…

十万部冷知识:日本国歌为什么像哀乐?

大家在世界杯上看日本队比赛的时候&#xff0c;有没有感觉他们的国歌跟哀乐似的&#xff0c;听着就跟在办葬礼一样。其实&#xff0c;这还真不是像与不像的问题&#xff0c;而是因为它确实是一首挽歌。 这首歌叫《君之代》&#xff0c;出自于《古今和歌集》&#xff0c;是在天皇…

关于Servlet编程(1)

1.Servlet编程中常见网页错误 404错误 : 访问不存在 一般都是路径出错. 405错误 : 请求方法不允许 使用访问的方法有误 只书写了接受Get方法的代码.却使用POST方法访问. 代码中忘记注释super()方法也会返回405 因为源码是直接返回405的 这里展示的两段代码都会引发上图的40…

[附源码]计算机毕业设计健康医疗体检Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…