【如何在Java中使用ForkJoinPool】

news2025/1/8 5:40:15

目录

  • 背景
  • 1.使用ForkJoinPool的线程池
  • 2.工作窃取算法
  • 3.ForkJoinPool的主要类
  • 4.使用递归操作
  • 5.资源任务
  • 6.何时使用ForkJoinPool
  • 7.总结

背景

使用ForkJoinPool去分解计算密集型任务且且并行地执行他们以获得更好的Java应用程序的性能。

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

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

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

1.使用ForkJoinPool的线程池

ForkJoinPool类存储工作线程Worker,Worker是在机器的每个CPU核心上运行的进程。每个进程都存储在deque中,deque代表双端队列。一旦工作线程的任务用完,它就开始从其他工作线程窃取任务。

首先,将有一个分叉任务的过程;这意味着一个大任务将被分解为可以并行执行的较小任务。所有子任务完成后,它们将重新加入。然后ForkJoinPool类提供一个结果,如图1所示。
在这里插入图片描述
当任务被提交到ForkJoinPool中,该进程将被划分为较小的进程,并推送到共享队列中。

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

最初,所有任务都将提交到一个主队列,该主队列将把任务推送到工作线程。请注意,任务是使用LIFO(后进先出)策略插入的,该策略与堆栈数据结构相同。

另一个重要的点是ForkJoinPool使用deques来存储任务。这提供了使用后进先出或先进先出(先进先出)的能力,这对于窃取工作的算法是必要的。
在这里插入图片描述

2.工作窃取算法

ForkJoinPool中的工作窃取是一种有效的算法,通过平衡池中所有可用线程的工作负载,可以有效地使用计算机资源。

当一个线程变为空闲时,它将尝试从仍忙于分配工作的其他线程中窃取任务,而不是保持非活动状态。这个过程最大限度地利用了计算资源,并确保没有线程负担过重,而其他线程保持空闲。
工作窃取算法背后的关键概念是,每个线程都有自己的任务组,并按后进先出的顺序执行。
当一个线程完成自己的任务并变为空闲时,它将尝试从另一个线程的deque的末尾“窃取”任务,遵循FIFO策略,与队列数据结构相同。这允许空闲线程拾取等待时间最长的任务,从而减少总体等待时间并提高吞吐量。

在下图中,线程2通过轮询线程1的deque中的最后一个元素,从线程1中窃取一个任务,然后执行该任务。被盗任务通常是deque中最古老的任务,这确保了工作负载在池中的所有线程之间均匀分布。
在这里插入图片描述
总的来说,ForkJoinPool的工作窃取算法是一个强大的功能,它可以通过确保所有可用的计算资源都得到有效利用来显著提高并行应用程序的性能。

3.ForkJoinPool的主要类

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

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

4.使用递归操作

要使用RecursiveAction功能,我们需要继承它并重写compute()方法。然后,我们用想要实现的逻辑创建子任务。

在下面的代码示例中,我们将以并行和递归的方式计算数组中每个数字的两倍。我们被限制为并行计算二乘二的数组元素。

正如您所看到的,fork()方法调用compute()方法。一旦整个数组的每个元素都得到了和,递归调用就会停止。一旦递归地求和了数组的所有元素,我们就会显示结果。

Listing 1. An example of RecursiveAction

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);

    // Invokes compute method
    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);

      // Invokes the compute method recursively
      leftArray.fork();
      rightArray.fork();

      // Joins results from recursive invocations
      leftArray.join();
      rightArray.join();
    }
  }
}

该计算的输出为252。

RecursiveAction需要记住的一点是,它不会返回值。通过使用分而治之的策略来提高性能,也可以打破这个过程。

这就是我们在清单1中所做的,我们不是计算每个数组元素的二重,而是通过将数组分解为多个部分来并行计算。

同样重要的是要注意,RecursiveAction在用于可以有效分解为较小子问题的任务时最有效。
因此,RecursiveAction和ForkJoinPool应该用于计算密集型任务,在这些任务中,工作的并行化可以显著提高性能。否则,由于线程的创建和管理,性能将更差。

5.资源任务

在下一个示例中,让我们研究一个简单的程序,它递归地在中间中断,直到达到基本条件。在本例中,我们使用的是RecursiveTask类。
RecursiveAction和RecursiveTask的区别在于,使用递归任务,我们可以在compute()方法中返回一个值。
Listing 2. An example of RecursiveTask

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发送到ForkJoinsMarrayTask,然后我们分叉task1,它并行执行compute()方法和数组的其他部分。

一旦递归过程达到基本条件,就会调用连接方法,连接结果。
这种情况下的输出为25。

6.何时使用ForkJoinPool

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

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

7.总结

在本文中,您了解了如何使用最重要的ForkJoinPool功能在单独的CPU核心中执行繁重的操作。让我们以这篇文章的要点作为结论:

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

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

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

相关文章

程序员进银行科技岗——简单总结

银行的分类 Top0—中央银行&#xff1a; 仅有一家&#xff0c;即中国人民银行。 Top1—政策性银行&#xff1a; 国家开发银行、中国进出口银行、中国农业发展银行 Top2—国有商业银行&#xff1a; 国有六大行&#xff08;中国工商银行、中国农业银行、中国银行、中国建设…

【计算机网络】前后端分离,HTTP协议,网络分层结构,TCP

❤️ Author&#xff1a; 老九 ☕️ 个人博客&#xff1a;老九的CSDN博客 &#x1f64f; 个人名言&#xff1a;不可控之事 乐观面对 &#x1f60d; 系列专栏&#xff1a; 文章目录 前后端分类HTTP协议HTTP组成HTTP的版本HTTP的请求方式HTTP请求头HTTP 响应状态码 AJAX发送请求 …

555定时器的基本原理和应用案例

前言 555定时器常用于脉冲波形的产生和整形电路中&#xff0c;之前在查找555定时器的原理图和基本管脚信息时&#xff0c;网上的内容大多含糊不清&#xff0c;没有讲的很详细&#xff0c;要么只是单一的管脚图&#xff0c;要么就是简单的文字解释&#xff0c;并且大多数缺乏基…

2023 年大厂实习前端面试题(一):跨域问题

1. 跨域 1.1 跨域问题来源 跨域问题的来源是浏览器为了请求安全而引入的基于同源策略&#xff08;Same-origin policy&#xff09;的安全特性。 同源策略是浏览器一个非常重要的安全策略&#xff0c;基于这个策略可以限制非同源的内容与当前页面进行交互&#xff0c;从而减少…

linux 条件变量 pthread_cond_signal

专栏内容&#xff1a;linux下并发编程个人主页&#xff1a;我的主页座右铭&#xff1a;天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物&#xff0e; 目录 前言 简介 应用场景 与互斥量/信号量的区别 接口介绍 变量定义 初始化 等待被唤…

ROS:ROS的一些基本命令行

目录 一、打开小海龟1.1终端&#xff0c;启动ROS Master&#xff1a;1.2终端2&#xff0c;启动小海龟仿真器&#xff1a;1.3终端3&#xff0c;启动海龟控制节点&#xff1a; 二、查看系统中的计算图三、节点命令3.1查看节点下的命令rosnode3.2显示节点列表rosnode list3.3查看节…

[CISCN2023]unzip

[CISCN2023]unzip 环境搭建 1.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> </head> <body><form method"post" action"1.php" en…

Java 基础进阶篇(十六):多线程总结

文章目录 一、多线程概述二、多线程的创建1.1 方式一&#xff1a;继承 Thread 类1.2 方式二&#xff1a;实现 Runnable 接口匿名内部类实现方案 1.3 方式三&#xff1a;JDK 5.0新增: 实现 Callable 接口1.4 三种方式对比 二、Thread的常用方法三、线程安全与同步3.1 线程安全3.…

数据类型.

数据类型 数据类型分类 数值类型 tinyint类型 数值越界测试&#xff1a; mysql> create table tt1(num tinyint); Query OK, 0 rows affected (0.02 sec)mysql> insert into tt1 values(1); Query OK, 1 row affected (0.00 sec)mysql> insert into tt1 values(128…

数据仓库基础(通俗易懂,好文)数仓概念

1、数据仓库的概念 数据仓库&#xff08;英语&#xff1a;Data Warehouse&#xff0c;简称数仓、DW&#xff09;,是一个用于存储、分析、报告的数据系统。数据仓库的目的是构建面向分析的集成化数据环境&#xff0c;为企业提供决策支持&#xff08;Decision Support&#xff09…

CISCN WP ——R3vCr4ck

[CISCN-Misc] 签到卡 [CISCN-Misc] 被加密的生产流量 在过滤器中搜索modbus 发现类似base的编码 跟踪TCP流得到Base32密文 在线解密 [CISCN-Crypto]可信度量 非预期解 分析题目&#xff0c;发现修改程序后的测试程序位于容器内&#xff0c;使用winscp通过scp连接容器&#xff…

Flume系列:案例-Flume复制(Replicating)和多路复用(Multiplexing)

目录 Apache Hadoop生态-目录汇总-持续更新 1&#xff1a;案例流程描述 2&#xff1a;实现步骤&#xff1a; 2.1&#xff1a;实现flume1.conf 2.2&#xff1a;实现flume2_hdfs.conf 2.3&#xff1a;实现flume3_dir.conf 3&#xff1a;启动传输链路 Apache Hadoop生态-目录…

移动端开发之基础知识

移动端开发之流式布局 移动端基础浏览器现状手机屏幕现状移动端调试方法 视口布局视口视觉视口理想视口总结&#xff1a; meta视口标签标准的viewport设置 三倍图物理像素&物理像素比多倍图背景缩放 background-size背景图三倍图 多倍图切图 cutterman 移动端开发选择移动端…

这么可爱的彩虹屁老婆,真的不想“娶”一个放桌面上吗?

&#x1f4a7;这么可爱的 彩虹屁老婆 \color{#FF1493}{彩虹屁老婆} 彩虹屁老婆&#xff0c;真的不想“娶”一个放桌面上吗&#xff1f;&#x1f4a7; &#x1f337; 仰望天空&#xff0c;妳我亦是行人.✨ &#x1f984; 个人主页——微风撞见云的博客&#x1f390; &…

如何在华为OD机试中获得满分?Java实现【比赛评分】一文详解!

✅创作者:陈书予 🎉个人主页:陈书予的个人主页 🍁陈书予的个人社区,欢迎你的加入: 陈书予的社区 🌟专栏地址: Java华为OD机试真题(2022&2023) 文章目录 1. 题目描述2. 输入描述3. 输出描述4. Java算法源码5. 测试6.解题思路1. 题目描述 一个有N个选手参加比赛,…

信号处理与分析-卷积的性质与推导

目录 一、引言 二、信号分析中的卷积 1. 什么是卷积 2. 卷积的性质 3. 卷积的应用 三、离散卷积 1. 离散卷积的定义 2. 离散卷积的计算 3. 离散卷积的性质 四、连续卷积 五、卷积的实际应用 六、总结 一、引言 在信号处理中&#xff0c;卷积是一种非常重要的数学运…

如何在华为OD机试中获得满分?Java实现【吃到最多的刚好合适的菜】一文详解!

✅创作者:陈书予 🎉个人主页:陈书予的个人主页 🍁陈书予的个人社区,欢迎你的加入: 陈书予的社区 🌟专栏地址: Java华为OD机试真题(2022&2023) 文章目录 1. 题目描述2. 输入描述3. 输出描述4. Java算法源码5. 测试6.解题思路1. 题目描述 入职后,导师会请你吃饭…

day2 -- MySQL内部模块

学习目标 我希望了解一下Mysql的工作原理&#xff0c;实现这个工作原理的各个模块是如何协同工作的。 学习内容 服务端与客户端 服务端与客户端如何通信 存储引擎 存储结构 具体细节 这里先放上Mysql可视化结构&#xff0c;来自B站 服务端 服务端也就是我们常说的Mysql&…

多元回归预测 | Matlab蛇群算法(SO)优化最小二乘支持向量机回归预测,SO-LSSVM回归预测,多变量输入模型

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 多元回归预测 | Matlab蛇群算法(SO)优化最小二乘支持向量机回归预测,SO-LSSVM回归预测,多变量输入模型 评价指标包括:MAE、RMSE和R2等,代码质量极高,方便学习和替换数据。要求2018版本及以上。 部分源码 %---…

基于博奇编码的计算全息图及再现研究

一、引言 全息技术作为一种新的成像技术近年来得到迅速的发展&#xff0c;计算机制全息图不需要实物的存在&#xff0c;同时还能通过计算机实现像的再现。计算全息图主要包括迂回位相型计算全息图和修正离轴参考光计算全息图&#xff0c;这两类全息图由不同的编码方式得到。前…