ForkJoinPool + RecursiveTask 来计算数组元素和

news2024/11/20 4:45:07

ForkJoinPool 是什么?

ForkJoinPool 是一个 Java 并发编程框架,用于解决分治算法中的任务拆分、执行、合并等问题,是 Java 7 引入的一个新的工具类。

ForkJoinPool 的基本思想是将一个大任务划分成若干个小任务,然后并行执行这些小任务,最后将它们的结果合并起来得到最终结果。ForkJoinPool 的实现采用了工作窃取算法,即当某个线程完成自己的任务后,会主动从其他线程的任务队列中“窃取”任务执行,以充分利用 CPU 的计算资源,提高程序的并行度和性能。

使用 ForkJoinPool 可以通过简单的几行代码就实现高效的并发任务执行,尤其适合处理递归式的任务。在 Java 8 中,Stream API 内部就是基于 ForkJoinPool 实现的。

什么是RecursiveTask ?

RecursiveTask是Java中Fork/Join框架提供的一个类,用于实现可递归地将一个任务拆分成多个小任务的任务。它是ForkJoinTask的一个子类,表示一个可分解的异步任务,通常用于处理需要分治的问题,如归并排序、快速排序等。

实现RecursiveTask需要重写它的compute()方法,该方法将问题分解成较小的子问题,每个子问题都会被分配到一个线程池中执行,并返回一个子问题的结果。当所有子问题的结果都被收集后,compute()方法会将它们合并成一个完整的结果,这个结果就是该任务的最终结果。

RecursiveTask与普通的ForkJoinTask的区别在于,它的compute()方法返回值是一个泛型类型的结果,而不是void类型。在处理可分解的问题时,RecursiveTask会递归地将问题拆分成多个小问题,并使用fork()方法将它们提交到线程池中执行,然后使用join()方法等待子任务的结果,最后将所有子任务的结果合并成一个大的结果。因此,RecursiveTask常常被用于处理有返回结果的可分解问题,而ForkJoinTask则常常被用于处理无返回结果的可分解问题。

为什么有了ForkJoinPool 还要RecursiveTask?

ForkJoinPool 和 RecursiveTask 是 Java 并发编程中常用的工具,它们都用于实现并行化的任务执行,但它们的应用场景略有不同。

ForkJoinPool 是一个线程池,用于执行可以被拆分为更小任务的大型任务。在一个 ForkJoinPool 中,任务被分成多个子任务并由多个工作线程并行执行。每个子任务都是 ForkJoinTask 类的实例,它们可以是 ForkJoinTask 的子类或者是它的两个子类之一:RecursiveAction 和 RecursiveTask。

RecursiveTask 是 ForkJoinTask 的一个子类,它代表一个可以被拆分为更小任务并返回结果的任务。相比 RecursiveAction,RecursiveTask 可以返回一个结果,适用于需要在任务中返回结果的场景。

虽然 ForkJoinPool 可以执行任何类型的 ForkJoinTask,但是 RecursiveTask 是专门用于返回结果的任务,因此在需要返回结果的任务中应该使用 RecursiveTask 而不是其他类型的 ForkJoinTask。同时,RecursiveTask 中也可以拆分任务,利用 ForkJoinPool 的线程池机制实现更高效的并行化任务执行。

RecursiveAction 是 ForkJoinTask 的一个子类,用于代表一个可以被拆分为更小任务但不返回结果的任务。相比 RecursiveTask,RecursiveAction 不会返回一个结果,适用于不需要返回结果的场景。

适用于 RecursiveAction 的任务通常涉及对数据结构的修改或者执行某些副作用,例如对一个列表进行排序或者对一个数组进行求和。这些任务可以被拆分为多个子任务,并由 ForkJoinPool 中的多个工作线程并行执行,以提高任务执行的效率。

ForkJoinTask 的子类或者是它的两个子类之一:RecursiveAction 和 RecursiveTask。区别:
RecursiveAction :RecursiveAction 不会返回一个结果,适用于不需要返回结果的场景。用于代表一个可以被拆分为更小任务但不返回结果的任务。
RecursiveTask:RecursiveTask 可以返回一个结果,适用于需要在任务中返回结果的场景。用于代表一个可以被拆分为更小任务并返回结果的任务。

第一步:定义一个RecursiveTask 子类以重写 compute () 方法

package com.lfsun.main.point.democoncurrent;

import java.util.concurrent.RecursiveTask;

/**
 * 计算数组元素和的任务
 * <p>
 * RecursiveTask 是一个抽象类,它表示一个可以通过递归地将任务拆分为更小的子任务来并行执行的任务。
 * <p>
 * 具体来说,这个 SumTask 类表示一个计算长整型数列元素和的任务。
 * 它需要实现 RecursiveTask 类的 compute() 方法来完成计算任务,并返回计算结果。
 * 在实现 compute() 方法时,这个类可能会将大的数列拆分为多个子任务,并通过调用 fork() 方法来提交这些子任务,
 * 然后通过调用 join() 方法来等待子任务的完成并合并它们的计算结果。
 * <p>
 * 在这个 SumTask 类中,递归的拆分过程可能会继续下去,直到数列被拆分成足够小的子问题,
 * 这些子问题可以直接计算出其结果,而不需要再进行拆分。
 * 这些直接可计算的子问题的计算结果将被累加到最终的计算结果中。
 *
 * @author Administrator
 */
public class SumTask extends RecursiveTask<Long> {

    /**
     * 小任务阈值
     */
    private static final int THRESHOLD = 5000000;

    /**
     * 待计算的数组
     */
    private final long[] array;

    /**
     * 待计算的数组下标区间 [low, high]
     */
    private final int low;
    private final int high;

    public SumTask(long[] array, int low, int high) {
        this.array = array;
        this.low = low;
        this.high = high;
    }

    /**
     * 计算任务
     */
    @Override
    protected Long compute() {
        long sum = 0L;
        // 如果阈值设置得过小,会导致过多的任务拆分,而拆分后的子任务过小,从而导致线程调度和任务切换的开销过大,反而降低了程序的效率。
        // 如果阈值设置得过大,会导致任务过于庞大,无法充分利用系统资源进行并行计算,也会降低程序的效率。
        if (high - low <= THRESHOLD) {
            // 如果区间长度小于等于阈值,则直接计算
            for (int i = low; i <= high; i++) {
                sum += array[i];
            }
        } else {
            // 否则,将任务分解成两个子任务,递归计算

            // >> 1 是右移一位,等价于除以2,不过它是有符号右移,也就是如果原来的数是负数,那么移位之后仍然是负数,会在高位补上符号位。
            //比如:-3 的二进制表示为 1111 1101,在使用 >> 1 右移一位后变成 1111 1110,对应的十进制数是 -2。
            //而无符号右移 >>> 1 则不考虑符号位,直接右移,高位补 0,因此对于正数的结果和有符号右移一致,对于负数则会得到一个很大的正数,因为最高位变成了0。
            int mid = (low + high) >>> 1;
            SumTask left = new SumTask(array, low, mid);
            SumTask right = new SumTask(array, mid + 1, high);
            // left.fork() 方法和 right.fork() 方法都是用来提交子任务的,
            // 它们的作用是将当前任务分成若干个子任务,并将这些子任务提交到 Fork/Join 框架中进行执行。
            // 当一个任务被提交到 Fork/Join 框架中时,框架会根据任务的大小和复杂度来决定是否将任务继续拆分,
            // 以及如何分配任务给线程池中的不同线程来执行。
            left.fork();
            right.fork();
            // 使用 join() 方法获取子任务的计算结果,并将这些结果合并成整个任务的计算结果。
            // 在 Fork/Join 框架中,join() 方法用于等待子任务完成并返回子任务的计算结果。

            // 在执行 sum = left.join() + right.join() 时,程序会阻塞等待子任务的完成,
            // 并在所有子任务完成后才会继续执行计算结果的合并。这样,就可以保证整个数列的和的计算是正确的。
            sum = left.join() + right.join();
        }
        return sum;
    }
}

第二步:测试

import com.google.common.base.Stopwatch;
import com.lfsun.common.util.MyArraysUtils;
import org.junit.jupiter.api.Test;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;

class SumTaskTest {

    @Test
    void compute() throws ExecutionException, InterruptedException {
        int size = 10000000;
        long[] array = MyArraysUtils.genArray(size);


        // 创建计时器\StopWatch 是一个计时器类,用于测量代码块的执行时间。
        Stopwatch stopWatch = Stopwatch.createStarted();
        long sumFor = 0;
        for (int i = 0; i < size; i++) {
            sumFor += array[i];
        }
        // elapsed() 方法是 StopWatch 类的一个公共方法,用于返回计时器已经过去的时间,以指定的时间单位为单位。
        System.out.printf("结果 %s 耗时 %sms%n", sumFor, stopWatch.elapsed(TimeUnit.MILLISECONDS));

        // reset() 方法是 StopWatch 类的一个公共方法,用于将计时器重置为初始状态,即清零计时器的计时值和状态。
        stopWatch.reset();

        // 创建任务
        SumTask sumTask = new SumTask(array, 0, size - 1);


        // 创建线程池 ForkJoinPool 是一个基于工作窃取算法的线程池实现,用于执行可以被拆分成更小任务并在多个线程中并行执行的任务。
        // ForkJoinPool.commonPool() 是一个静态方法,用于获取一个全局共享的 ForkJoinPool 线程池实例,这个线程池实例会被多个任务共享。
        // 工作窃取算法:一种用于并行计算的调度算法:让每个线程都有一个私有的任务队列,当线程完成自己的任务时,
        //          它可以从其他线程的队列中“窃取”任务并执行。
        //          在这种方式下,线程之间的任务负载可以自适应地平衡,从而更好地利用系统资源,提高程序的并行性能。
        ForkJoinPool forkJoinPool = ForkJoinPool.commonPool();

        // 提交任务到线程池,ForkJoinPool 线程池会在后台异步地执行这个任务。
        forkJoinPool.submit(sumTask);

        // 获取结果
        Long result = sumTask.get();
        System.out.printf("结果 %s 耗时 %sms%n", result, stopWatch.elapsed(TimeUnit.MILLISECONDS));
    }
}

第三步:结果(可见其速度差异)

在这里插入图片描述

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

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

相关文章

SQL AVG函数

SQL AVG函数 SQL AVG函数简介 SQL AVG函数是一个聚合函数&#xff0c;用于计算集合的平均值。 以下说明了SQL AVG函数的语法&#xff1a; AVG([ALL|DISTINCT] expression)如果使用ALL关键字&#xff0c;AVG函数将获取计算中的所有值。 默认情况下&#xff0c;无论是否指定&a…

python+vue 图书馆读者行为分析系统-书友会

本系统主要包括以下功能模块&#xff1a;个人中心、用户管理、图书信息管理、图书分类管理、热门图书管理、书友会管理、报名信息管理、行为分析管理、在线论坛、系统管理等模块&#xff0c;通过这些模块的实现能够基本满足日常图书馆读者行为分析系统的操作。结合相关设计模式…

生成式 AI 与强人工智能:探索 AI 技术的未来

AIGC&#x1f388; AIGC&#xff08;AI Generated Content&#xff09; 即人工智能生成内容&#xff0c;又称“生成式 AI”&#xff08;Generative AI&#xff09;&#xff0c;被认为是继专业生产内容&#xff08;PGC&#xff09;、用户生产内容&#xff08;UGC&#xff09;之…

rust中的集合容器(切片和哈希)与错误处理

String、数组[T:n]、列表Vec\哈希表HashMap<K,V>等。 切片slice&#xff1b; 循环缓冲区 VecDeque、双向列表 LinkedList等。(这是指双向链表吗&#xff1f;) 这些集合容器的共性&#xff1a; 可以遍历 可以进行 map-reduce操作。 可以从一种类型转换成另一种类型。 主要…

VUE使用el-ui的form表单输入框批量搜索<VUE专栏三>

针对form表单的输入框单号批量查询&#xff0c;这里用换行符进行分割&#xff0c;注意v-model不要使用.trim 前端代码&#xff1a; <el-form-item label"SKU编码:" prop"prodNumbers"><el-input type"textarea" :rows"4" pla…

阿里数学竞赛决赛名单公布:北大人数是清华4倍 | 最小仅14岁

4月10日消息&#xff0c;第二届阿里巴巴全球数学竞赛决赛入围名单公布&#xff0c;全球12个国家516位选手晋级&#xff0c;晋级率仅有1&#xff05;。 根据参赛者填报信息&#xff0c;晋级选手80&#xff05;以上是90后&#xff0c;年纪最小的只有14岁。 入围人数最高的前20所高…

【Linux】git命令(基础,新手)

文章目录1.查看当前git版本信息2.安装git3.将远端仓库克隆到本地4.三板斧第一招&#xff1a;git add5.三板斧第二招&#xff1a;git commit6.三板斧第三招&#xff1a;git push7.对仓库文件进行更改8.查看使用提交日志9.查看本地与远端的同步状态10.从远端仓库拉取最新版本文件…

ChatGPT Plus价格太贵,可以约上三五知己一起上车体验一下,这个项目就能帮到你

❝ 对于想体验ChatGPT PLus的小伙伴&#xff0c;可能觉得自己一个人一个月花费20美元&#xff0c;相对于人民币每月137多&#xff0c;确实是一个不少的开支&#xff0c;如果&#xff0c;几个人合作一个账号&#xff0c;这样负担就减少了。刚好&#xff0c;最近逛github发现刚好…

深度学习-第R2周——LSTM火灾温度预测

深度学习-第R2周——LSTM火灾温度预测深度学习-第R2周——LSTM火灾温度预测一、前言二、我的环境三、前期工作1、导入数据集2、数据可视化四、构建数据集1、设置x,y2、归一化3、划分数据集五、构建模型六、模型训练1、编译2、训练七、评估1、loss图2、预测深度学习-第R2周——L…

MySQL数据库实现主主同步

前言 MySQL主主同步实际上是在主从同步的基础上将从数据库也提升成主数据库&#xff0c;让它们可以互相读写数据库&#xff0c;从数据库变成主数据库&#xff1b;主从相互授权连接&#xff0c;读取对方binlog日志并更新到本地数据库的过程,只要对方数据改变&#xff0c;自己就…

K均值聚类分析流程

K均值聚类分析流程 一、案例背景 在某体育赛事中&#xff0c;意大利、韩国、罗马尼亚、法国、中国、美国、俄罗斯七个国家的裁判对300名运动员进行评分&#xff0c;现在想要通过评分上的差异将300名选手进行分类&#xff0c;计划将选手分为高水平、中水平、低水平三个类别。因…

Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

00 网址 来源 siki学院的&#xff08;1年有限期到期前下载的项目&#xff0c;现在已经过期&#xff0c;所以自己理清项目&#xff09; 所以更多的不是学习这个项目&#xff0c;而是学习理清该类型的项目的思路 Unity2D 商业游戏案例 - 梦幻西游&#xff08;第二季 框架设计篇&…

python+vue 在线考试系统的设计与实现

1.用户登录 用户要通过本系统查询对课程信息进行下载&#xff0c;必须先输入用户名和密码进行登陆。为了避免非其他人员都可以获得登陆权限&#xff0c;登陆系统不设注册过程&#xff0c;所有用户和教师的登陆信息将事先由管理人员直接对数据库进行录入。 2.教师 教师登录系统后…

【排序】排序这样写才对Ⅱ -冒泡排序与快速排序Ⅰ

Halo&#xff0c;这里是Ppeua。平时主要更新C语言&#xff0c;C&#xff0c;数据结构算法......感兴趣就关注我吧&#xff01;你定不会失望。 &#x1f308;个人主页&#xff1a;主页链接 &#x1f308;算法专栏&#xff1a;专栏链接 我会一直往里填充内容哒&#xff01; &…

【Spring6】| Spring6整合JUnit

目录 一&#xff1a;Spring6整合JUnit 1. Spring对JUnit4的支持 2. Spring对JUnit5的支持 一&#xff1a;Spring6整合JUnit 1. Spring对JUnit4的支持 准备工作&#xff1a;pom.xml 注&#xff1a;以前是直接使用单元测试Junit&#xff0c;现在使用Spring对Junit的整合&…

快递电子运单上,电话应隐藏6位以上,禁止显示这些信息

我国快递年业务量达千亿件&#xff0c;快递电子运单是应用于快递外包装的重要单据&#xff0c;每年耗用量很大。在强化个人信息保护方面&#xff0c;《快递电子运单》国家标准要求快递企业、电商经营主体等采取措施&#xff0c;避免在电子运单上显示完整的收寄件人个人信息。 …

【机器学习】P14 Tensorflow 使用指南 Dense Sequential Tensorflow 实现

Tensorflow 第一节&#xff1a;使用指南Tensorflow 安装神经网络一些基本概念隐藏层和输出层&#xff1a;神经元的输出公式Tensorflow 全连接层 Dense 与 顺序模型 SequentialDense LayerSequential Model代码实现一个神经网络实现方式一&#xff1a;手写神经网络* 实现方式二&…

JavaScript基础入门全解析(上)

JavaScript基础语法 什么是JavaScript&#xff08;简称js&#xff09; 1.首先了解前端页面的组成&#xff08;前端页面的三层结构&#xff09; ●HTML 表示了你的页面内有什么&#xff0c;组成页面的骨架 &#xff08;结构层&#xff09; ●CSS 表示了你的页面中每一个内容是…

Linux系统中安装新版本nacos(centos7)

1. 背景需求 由于一些限制,在客户现场的Linux操作系统中,没有安装docker k8s等容器,无法直接使用镜像安装,而且客户要求只能在原始的操作系统中安装最新版的nacos,(为什么需要安装最新版的nacos,因为检测国网检测到之前版本的nacos有漏洞,需要安装新版的nacos). 2. 下载nacos…

Windows10+Cmake+VS2019编译opencv

主要参考&#xff1a;Windows10CmakeVS2019编译opencv&#xff08;超级详细&#xff09;_vs编译opencv_乐安世家的博客-CSDN博客 OpenCV&#xff1a;Releases - OpenCV 想直接简单使用的话&#xff0c;不需要自己编译&#xff0c;下载编译好的就可以 假如需要用到opencv-contr…