ForkJoinPool 你真的明白和用对了吗

news2024/11/14 20:57:38

ForkJoinPool 是一个功能强大的 Java 类,用于处理计算密集型任务,使用 ForkJoinPool 分解计算密集型任务,并并行执行它们,能够产生更好的性能。它的工作原理是将任务分解成更小的子任务,使用分而治之的策略进行操作,使其能够并发地执行任务,从而提高吞吐量并减少处理时间。

ForkJoinPool 的独特特性之一是它用于优化性能的工作窃取算法。当工作线程完成分配的任务时,它将从其他线程窃取任务,确保所有线程都有效地工作,并且不会浪费计算机资源。

ForkJoinPool 在 Java 的并行流和 CompletableFutures 中广泛使用,允许开发人员轻松地并发执行任务。此外,其他 JVM 语言(如 Kotlin和 Akka)也使用这个框架来构建需要高并发性和弹性的消息驱动应用程序。

使用 ForkJoinPool 构建线程池

ForkJoinPool 存储着 worker,这些 worker 是在机器的每个 CPU 核心上运行的进程。这些进程中的每一个都存储在一个双端队列(Deque)中。一旦工作线程的任务用完,它就开始从其他工作线程窃取任务。

首先,会有分岔任务的过程。这意味着一个大任务将被分解成可以并行执行的小任务。一旦所有子任务完成,它们就会重新加入。最后,ForkJoinPool 类通过 Join 的方式提供一个输出结果,如下图所示。

无标题-2023-06-15-1917 (1).png

当任务在 ForkJoinPool 中提交时,该进程将被分成更小的进程并推送到共享队列中。

一旦 fork() 方法被调用,任务将被并行调用,直到基本条件为真。一旦处理被分叉,join() 方法会确保线程相互等待,直到进程完成。

所有任务最初都将提交给一个主队列,这个主队列将把任务推送给 work 线程。同时,与堆栈数据结构相同,任务是使用后进先出(LIFO)策略插入的,如下图所示。

无标题-2023-06-15-1917 (2).png

Work-stealing 窃取算法

Work-stealing 算法是一种用于实现并行计算和负载平衡的策略。它通常用于在分布式系统和多核处理器中,以高效地分配和平衡计算任务。

Work-stealing 算法的优点是它可以实现高效的负载平衡和并行计算,同时减少了任务的等待时间。当一个线程完成自己的任务并变得空闲时,它将尝试从另一个线程的队列末端“窃取”任务,与队列数据结构相同,它遵循 FIFO 策略。这种策略将允许空闲线程拾取等待时间最长的任务,从而减少了总体等待时间并提高了吞吐量。

在下面的图中,线程 2 通过轮询线程 1 的队列中的最后一个元素,从线程 1 窃取一个任务,然后执行该任务。被窃取的任务通常是队列中等待时间最长的的任务,这确保了工作负载在池中的所有线程之间均匀分布。

无标题-2023-06-15-1917 (3).png

总的来说,ForkJoinPool 的工作窃取算法是一个强大的功能,可以通过确保所有可用的计算资源得到有效利用来显著提高并行程序的性能。

ForkJoinPool 主类

让我们快速浏览一下支持使用 ForkJoinPool 进行处理的主类。

  • ForkJoinPool 创建一个线程池来使用 ForkJoin:它的工作原理与其他线程池类似。这个类中最重要的方法是 commonPool(),它用于创建了 ForkJoin 线程池。
  • RecursiveAction:该类的主要功能是计算递归操作。在 compute() 方法中,我们没有返回值,这是因为递归发生在 compute() 方法中。
  • RecursiveTask: 这个类的工作方式类似于 RecursiveAction,不同之处在于 compute() 方法将返回一个值。

使用 RecursiveAction

要使用 RecursiveAction 的功能,我们需要继承它并覆盖它的 compute() 方法。

在下面的代码示例中,我们将以并行和递归的方式计算数组中每个数字的两倍数。

我们看到在代码中,fork() 方法调用 compute() 方法。一旦整个数组得到了每个元素的和,递归调用就停止了。同时,一旦对数组的所有元素进行递归求和,我们就会显示结果。

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;

public class ForkJoinDoubleAction {

  public static void main(String[] args) {
    ForkJoinPool forkJoinPool = new ForkJoinPool();
    int[] array = {1, 5, 10, 15, 20, 25, 50};
    DoubleNumber doubleNumberTask = new DoubleNumber(array, 0, array.length);

    // 调用compute方法
    forkJoinPool.invoke(doubleNumberTask);
    System.out.println(DoubleNumber.result);
  }
}

class DoubleNumber extends RecursiveAction {

  final int PROCESS_THRESHOLD = 2;
  int[] array;
  int startIndex, endIndex;
  static int result;

  DoubleNumber(int[] array, int startIndex, int endIndex) {
    this.array = array;
    this.startIndex = startIndex;
    this.endIndex = endIndex;
  }

  @Override
  protected void compute() {
    if (endIndex - startIndex <= PROCESS_THRESHOLD) {
      for (int i = startIndex; i < endIndex; i++) {
        result += array[i] * 2;
      }
    } else {
      int mid = (startIndex + endIndex) / 2;
      DoubleNumber leftArray = new DoubleNumber(array, startIndex, mid);
      DoubleNumber rightArray = new DoubleNumber(array, mid, endIndex);

      // 递归地调用compute方法
      leftArray.fork();
      rightArray.fork();

      // Joins
      leftArray.join();
      rightArray.join();
    }
  }
}

计算的结果输出是 252。

从 RecursiveAction 中要记住的重要一点是,它不返回值。还可以通过使用分而治之的策略来分解这个过程,从而提高性能。

同样要注意的是,当将 RecursiveAction 用于可以有效地分解为更小的子问题的任务时,它是最有效的。

因此,RecursiveAction 和 ForkJoinPool 应该用于计算密集型任务,在这些任务中,工作的并行化可以显著提高性能。否则,由于线程的创建和管理,性能会变得更差。

RecursiveTask

在这个示例中,我们使用 RecursiveTask 类看看有没有什么区别。

RecursiveAction 和 RecursiveTask 之间的区别在于,使用 RecursiveTask,我们可以在compute() 方法中返回一个值。

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

public class ForkJoinSumArrayTask extends RecursiveTask<Integer> {

  private final List<Integer> numbers;

  public ForkJoinSumArrayTask(List<Integer> numbers) {
    this.numbers = numbers;
  }

  @Override
  protected Integer compute() {
    if (numbers.size() <= 2) {
      return numbers.stream().mapToInt(e -> e).sum();
    } else {
      int mid = numbers.size() / 2;
      List<Integer> list1 = numbers.subList(0, mid);
      List<Integer> list2 = numbers.subList(mid, numbers.size());
 
      ForkJoinSumArrayTask task1 = new ForkJoinSumArrayTask(list1);
      ForkJoinSumArrayTask task2 = new ForkJoinSumArrayTask(list2);

      task1.fork();

      return task1.join() + task2.compute();
    }
  }

  public static void main(String[] args) {
    ForkJoinPool forkJoinPool = new ForkJoinPool();

    List<Integer> numbers = List.of(1, 3, 5, 7, 9);
    int output = forkJoinPool.invoke(new ForkJoinSumArrayTask(numbers));

    System.out.println(output);
  }
}

在上面的代码中,我们递归地分解数组,直到它达到基本条件。

一旦我们破坏了主数组,我们就会将 list1 和 list2 发送给 ForkJoinSumArrayTask,然后我们分叉 task1,它将并行执行 compute() 方法和数组的其他部分。

一旦递归过程达到基本条件,就会调用 join 方法,将结果连接起来。

最后输出结果为 25。

何时使用 ForkJoinPool

ForkJoinPool 不应该在所有情况下都使用。如前所述,最好将其用于高度密集的并发进程。让我们具体看看这些情况都有哪些:

  • 递归任务: ForkJoinPool 非常适合执行递归算法,如快速排序、归并排序或二进制搜索。这些算法可以分解成更小的子问题并并行执行,显著提高性能。
  • 并行问题:如果你的问题可以很容易地划分为独立的子任务,例如图像处理或数值模拟,那么可以使用 ForkJoinPool 并行执行子任务。
  • 高并发场景:在高并发场景中,例如 web 服务器、数据处理管道或其他高性能应用程序,可以使用 ForkJoinPool 跨多个线程并行执行任务,这有助于提高性能和吞吐量。

结尾

在本文中,我们看到了如何使用最重要的 ForkJoinPool 功能在 CPU 内核中执行繁重的操作。最后让我们来总结本文的要点:

  • ForkJoinPool 是一个线程池,它使用分而治之策略递归地执行任务。
  • JVM 语言(如Kotlin和Akka)使用 ForkJoinPool 来构建消息驱动型的应用程序。
  • ForkJoinPool 并行执行任务,从而有效地利用计算机资源。
  • Work-stealing 窃取算法通过允许空闲线程从繁忙线程窃取任务来优化资源利用。
  • 任务存储在双端队列中,存储采用后进先出策略,窃取采用先进先出策略
  • ForkJoinPool 框架中的主要类包括 ForkJoinPool、RecursiveAction 和RecursiveTask:
    • RecursiveAction 用于计算递归操作,它不返回任何值。
    • RecursiveTask 用于计算递归操作,但返回一个值。
    • compute() 方法在两个类中被重写以实现自定义逻辑。
    • fork() 方法调用 compute() 方法并将任务分解为更小的子任务。
    • join() 方法等待子任务完成并合并它们的结果。
    • ForkJoinPool 通常与并行流和 CompletableFuture 一起使用。

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

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

相关文章

mysql数据传输到mssql

一、找开Navicat Premium 12 此时目标数据库会创建一个同名的表

ElasticSearch DSL语句(bool查询、算分控制、地理查询、排序、分页、高亮等)

文章目录 DSL 查询种类DSL query 基本语法1、全文检索2、精确查询3、地理查询4、function score &#xff08;算分控制&#xff09;5、bool 查询 搜索结果处理1、排序2、分页3、高亮 RestClient操作 DSL 查询种类 查询所有&#xff1a;查询所有数据&#xff0c;一般在测试时使…

【C++11新特性】lambda表达式

文章目录 1. lambda表达式概念2. lambda表达式语法3. lambda表达式应用 1. lambda表达式概念 lambda表达式是一个匿名函数&#xff0c;恰当使用lambda表达式可以让代码变得简洁&#xff0c;并且可以提高代码的可读性。 见见lambda表达式的使用 现在要对若干商品分别按照价格和…

Java并发----创建线程的三种方式及查看进程线程

一、直接使用 Thread // 创建线程对象 Thread t new Thread() {public void run() {// 要执行的任务} }; // 启动线程 t.start(); 例如&#xff1a; // 构造方法的参数是给线程指定名字&#xff0c;推荐 Thread t1 new Thread("t1") {Override// run 方法内实现…

LED驱动型IC芯片的原理介绍

一、LED驱动器是什么 LED驱动器&#xff08;LED Driver&#xff09;&#xff0c;是指驱动LED发光或LED模块组件正常工作的电源调整电子器件。由于LED PN结的导通特性决定&#xff0c;它能适应的电源电压和电流变动范围十分狭窄&#xff0c;稍许偏离就可能无法点亮LED或者发光效…

AraNet:面向阿拉伯社交媒体的新深度学习工具包

阿拉伯语是互联网上第四大最常用的语言&#xff0c;它在社交媒体上的日益增加为大规模研究阿拉伯语在线社区提供了充足的资源。然而&#xff0c;目前很少有工具可以从这些数据中获得有价值的见解&#xff0c;用于决策、指导政策、协助应对等。这种情况即将改变吗&#xff1f; …

Java日志框架-JUL

JUL全称Java util logging 入门案例 先来看着入门案例&#xff0c;直接创建logger对象&#xff0c;然后传入日志级别和打印的信息&#xff0c;就能在控制台输出信息。 可以看出只输出了部分的信息&#xff0c;其实默认的日志控制器是有一个默认的日志级别的&#xff0c;默认就…

串口通讯

USART是全双工同步通讯 在同步通信中&#xff0c;数据信号所传输的内容绝大多数属于有效数据&#xff0c;而异步通信中包含了各种帧的标识符&#xff0c;所以同步通讯的效率更高。但是同步通信对时钟要求苛刻&#xff0c;允许的误差小。而异步通信则允许双方的误差较大 比特率…

【MySQL系列】--初识数据库

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

币圈最后的黑暗时刻?也是牛市来临前的准备阶段!

最近加密市场的波动并不乐观&#xff0c;整体走势呈下行趋势&#xff0c;比特币价格跌至2.5万美元&#xff0c;以太坊更是跌破1500美元&#xff0c;其他山寨币也纷纷下挫&#xff0c;市场情绪相对悲观。 更令人担忧的是&#xff0c;当前加密市场缺乏新的叙事&#xff0c;也没有…

JVM学习笔记(一)

1. JVM快速入门 从面试开始&#xff1a; 请谈谈你对JVM 的理解&#xff1f;java8 的虚拟机有什么更新&#xff1f; 什么是OOM &#xff1f;什么是StackOverflowError&#xff1f;有哪些方法分析&#xff1f; JVM 的常用参数调优你知道哪些&#xff1f; 内存快照抓取和MAT分…

UAF释放后重引用原理

原地址&#xff1a;https://blog.csdn.net/qq_31481187/article/details/73612451 原作者代码是基于linux系统的演示代码&#xff0c;因为windows和Linux 内存管理机制上略有不同&#xff0c;该程序在Windows需要稍微做些改动。 Windows上执行free释放malloc函数分配的内存后…

javascript期末作业【三维房屋设计】 【源码+文档下载】

1、引入three.js库 官网下载three.js 库 放置目录并引用 引入js文件: 设置场景&#xff08;scene&#xff09; &#xff08;1&#xff09;创建场景对象 &#xff08;2&#xff09;设置透明相机 1,透明相机的优点 透明相机机制更符合于人的视角,在场景预览和游戏场景多有使用…

[gdc23]《战神:诸神黄昏》中的积雪系统

overview gdc23上santa monica带来基于tesselation的displacement map的可交互积雪系统&#xff0c;这是一个对于前作&#xff08;战神4&#xff09;的screen space parallax mapping的升级&#xff0c;而且是一个由自身render programmer在一个项目周期内&#xff0c;完成的&…

代码随想录算法训练营day38 | 70. 爬楼梯,509. 斐波那契数,746. 使用最小花费爬楼梯

目录 动态规划五部曲&#xff1a; 确定dp数组&#xff08;dp table&#xff09;以及下标的含义确定递推公式dp数组如何初始化确定遍历顺序举例推导dp数组 509. 斐波那契数 类型&#xff1a;动态规划 难度&#xff1a;easy 思路&#xff1a; f&#xff08;n&#xff09; f&am…

基于Echarts的大数据可视化模板:智慧门店管理

目录 引言智慧门店管理的重要性Echarts在智慧门店管理中的应用智慧门店概述定义智慧门店的概念和核心智慧门店的关键技术智慧门店的发展趋势与方向智慧门店管理的作用Echarts与大数据可视化Echarts库以及其在大数据可视化领域的应用优势开发过程和所选设计方案模板如何满足管理…

Slingshot | 细胞分化轨迹的这样做比较简单哦!~(一)

1写在前面 今天是医师节&#xff0c;祝各位医护节日快乐&#xff0c;夜班平安&#xff0c;病历全是甲级&#xff0c;没有错误。&#x1f970; 不知道各位医师节的福利是什么&#xff01;&#xff1f;&#x1f602; 我们医院是搞了义诊活动&#xff0c;哈哈哈哈哈哈哈。&#x1…

【SS927V100/22AP70超高清录像机SOC 4KP60】

SS927/22AP70 是一颗面向市场推出的专业超高清智 能网络录像机SoC。该芯片最高支持四路sensor 输入&#xff0c;支持最高4K60的ISP图像处理能力&#xff0c;支持 3F WDR、多级降噪、六轴防抖、硬件拼接等多种 图像增强和处理算法&#xff0c;为用户提供了卓越的图像 处理能力。…

归并排序:从二路到多路

前言 我们所熟知的快速排序和归并排序都是非常优秀的排序算法。 但是快速排序和归并排序的一个区别就是&#xff1a;快速排序是一种内部排序&#xff0c;而归并排序是一种外部排序。 简单理解归并排序&#xff1a;递归地拆分&#xff0c;回溯过程中&#xff0c;将排序结果进…

Servlet 初步学习

文章目录 Servlet1 简介2 快速入门3 执行流程4 生命周期5 方法介绍6 体系结构7 urlPattern配置8 XML配置 Servlet 1 简介 Servlet是JavaWeb最为核心的内容&#xff0c;它是Java提供的一门 动态 web资源开发技术。 使用Servlet就可以实现&#xff0c;根据不同的登录用户在页面…