您当前的位置:首页 > 电脑百科 > 程序开发 > 框架

掌握Fork/Join 框架,证明你Java毕业了

时间:2023-03-23 14:15:25  来源:今日头条  作者:迷路的架构师


 

请勇敢说出你毕业了!!!

1. 引言

在并发编程中,高效地利用多核处理器资源对于提高程序性能至关重要。为了简化并行任务的编写和管理,JAVA 7 引入了一种强大的框架:fork/Join 框架。Fork/Join 框架旨在帮助开发者更容易地实现分而治之(divide-and-conquer)策略,以解决大型计算问题。通过将问题分解为更小的任务,并将这些任务分配给多个处理器,Fork/Join 框架可以显著提高程序的执行速度。

本文将详细介绍 Java Fork/Join 框架的基本概念、工作原理以及如何使用它来解决实际问题。我们还将讨论如何优化 Fork/Join 框架的性能,以及它的局限性和替代方案。无论您是并发编程的初学者还是有经验的开发者,这篇文章都将帮助您更深入地了解并掌握 Java Fork/Join 框架。

2. Java Fork/Join 框架基础2.1 Fork/Join 的工作原理

Fork/Join 框架基于分而治之(divide-and-conquer)策略,将大型问题分解为更小、更易处理的子问题。这些子问题可以进一步细分,直到它们足够简单以便直接解决。然后,子问题的解决方案会被合并(Join),以形成原始问题的解决方案。

Fork/Join 框架利用了工作窃取算法,这意味着空闲的工作线程可以从其他线程的任务队列中“窃取”任务来执行。这种策略可以最大限度地利用处理器资源,从而提高程序的执行速度。

2.2 RecursiveTask 和 RecursiveAction

Fork/Join 框架提供了两个核心抽象类:RecursiveTask 和 RecursiveAction,它们分别表示返回结果和不返回结果的任务。要使用 Fork/Join 框架,您需要创建一个继承自这两个类之一的子类,并实现其 compute() 方法。在此方法中,您将编写逻辑来处理任务的分解和合并。

 

  • RecursiveTask:适用于需要返回结果的任务。它的 compute() 方法应返回一个类型为 V 的值。
  • RecursiveAction:适用于不需要返回结果的任务。它的 compute() 方法没有返回值。
2.3 Fork/Join 线程池(ForkJoinPool)

 

ForkJoinPool 是 Fork/Join 框架的线程池实现,它管理着一组工作线程,用于执行 RecursiveTask 和 RecursiveAction 任务。要执行任务,您需要创建一个 ForkJoinPool 实例,并调用其 invoke() 方法。您还可以使用静态方法 ForkJoinPool.commonPool() 获取一个公共的 ForkJoinPool 实例。

ForkJoinPool pool = new ForkJoinPool();MyRecursiveTask task = new MyRecursiveTask(someData);Result result = pool.invoke(task);3. 使用 Fork/Join 框架解决问题

要使用 Fork/Join 框架解决问题,您需要遵循以下步骤:

3.1 分而治之策略

将问题分解为更小、更易处理的子问题。这通常是通过递归实现的。在实现 RecursiveTask 或 RecursiveAction 子类的 compute() 方法时,首先检查任务是否足够简单,如果是,则直接解决。否则,将任务分解为更小的任务,并递归调用 compute() 方法。

3.2 设计递归任务

创建一个继承自 RecursiveTask 或 RecursiveAction 的子类,根据任务类型选择合适的抽象类。在子类中,实现 compute() 方法,其中包含任务分解和结果合并的逻辑。

class MyRecursiveTask extends RecursiveTask {private Data data;public MyRecursiveTask(Data data) {this.data = data;@Overrideprotected Result compute() {if (isSimpleEnough(data)) {return computeDirectly(data);MyRecursiveTask task1 = new MyRecursiveTask(splitData(data, 0));MyRecursiveTask task2 = new MyRecursiveTask(splitData(data, 1));task1.fork();Result result2 = task2.compute();Result result1 = task1.join();return mergeResults(result1, result2);3.3 合并结果

在 compute() 方法中,当任务分解到足够简单的程度时,直接计算结果。然后将子任务的结果合并以形成原始任务的解决方案。在上面的示例中,我们使用了 fork() 方法来异步执行 task1,而在当前线程中执行 task2。接着,我们使用 join() 方法等待 task1 的结果,然后将两个结果合并。

示例:计算数组的和class SumTask extends RecursiveTask {private static final int THRESHOLD = 500;private int[] array;private int start;private int end;public SumTask(int[] array, int start, int end) {this.array = array;this.start = start;this.end = end;@Overrideprotected Long compute() {if (end - start <= THRESHOLD) {Long sum = 0;for (int i = start; i < end; i++) {sum += array[i];return sum;int mid = (start + end) / 2;SumTask task1 = new SumTask(array, start, mid);SumTask task2 = new SumTask(array, mid, end);task1.fork();Long sum2 = task2.compute();Long sum1 = task1.join();return sum1 + sum2;4. Fork/Join 框架实例

在本节中,我们将通过几个实际示例来展示如何使用 Fork/Join 框架解决问题。

4.1 计算斐波那契数列

以下示例演示了如何使用 Fork/Join 框架计算斐波那契数列的第 n 项:

import java.util.concurrent.ForkJoinPool;import java.util.concurrent.RecursiveTask;public class FibonacciTask extends RecursiveTask {private final int n;public FibonacciTask(int n) {this.n = n;@Overrideprotected Integer compute() {if (n <= 1) {return n;FibonacciTask task1 = new FibonacciTask(n - 1);task1.fork();FibonacciTask task2 = new FibonacciTask(n - 2);return task2.compute() + task1.join();public static void mAIn(String[] args) {ForkJoinPool pool = new ForkJoinPool();int n = 10;FibonacciTask task = new FibonacciTask(n);int result = pool.invoke(task);System.out.println("The " + n + "th Fibonacci number is: " + result);4.2 归并排序

以下示例演示了如何使用 Fork/Join 框架实现归并排序算法:

import java.util.Arrays;import java.util.concurrent.ForkJoinPool;import java.util.concurrent.RecursiveAction;public class MergeSortTask extends RecursiveAction {private final int[] array;private final int low;private final int high;public MergeSortTask(int[] array, int low, int high) {this.array = array;this.low = low;this.high = high;@Overrideprotected void compute() {if (high - low <= 1) {return;int mid = (low + high) >>> 1;MergeSortTask leftTask = new MergeSortTask(array, low, mid);MergeSortTask rightTask = new MergeSortTask(array, mid, high);invokeAll(leftTask, rightTask);merge(array, low, mid, high);private void merge(int[] array, int low, int mid, int high) {int[] temp = Arrays.copyOfRange(array, low, mid);int i = low, j = mid, k = 0;while (i < j && j < high) {if (array[j] < temp[k]) {array[i++] = array[j++];} else {array[i++] = temp[k++];while (i < j) {array[i++] = temp[k++];public static void main(String[] args) {ForkJoinPool pool = new ForkJoinPool();int[] array = {5, 3, 1, 2, 6, 4};MergeSortTask task = new MergeSortTask(array, 0, array.length);pool.invoke(task);System.out.println("Sorted array: " + Arrays.toString(array));4.3 并行矩阵相乘

在本节中,我们将使用 Fork/Join 框架实现并行矩阵相乘。假设我们有两个矩阵 A 和 B,我们的任务是计算它们的乘积矩阵 C。

首先,我们创建一个继承自 RecursiveTask 的类 MatrixMultiplicationTask:

public class MatrixMultiplicationTask extends RecursiveTask {private static final int THRESHOLD = 64;private final double[][] A, B;private final int rowStart, rowEnd, colStart, colEnd;public MatrixMultiplicationTask(double[][] A, double[][] B, int rowStart, int rowEnd, int colStart, int colEnd) {this.A = A;this.B = B;this.rowStart = rowStart;this.rowEnd = rowEnd;this.colStart = colStart;this.colEnd = colEnd;@Overrideprotected double[][] compute() {

在 compute() 方法中,我们将实现任务的分解和合并逻辑:

@Overrideprotected double[][] compute() {int numRows = rowEnd - rowStart;int numCols = colEnd - colStart;if (numRows <= THRESHOLD && numCols <= THRESHOLD) {return multiplyMatricesDirectly();} else {private double[][] multiplyMatricesDirectly() {int numRows = rowEnd - rowStart;int numCols = colEnd - colStart;int numCommon = A[0].length;double[][] C = new double[numRows][numCols];for (int i = 0; i < numRows; i++) {for (int j = 0; j < numCols; j++) {double sum = 0;for (int k = 0; k < numCommon; k++) {sum += A[rowStart + i][k] * B[k][colStart + j];C[i][j] = sum;return C;

如果任务足够小(小于阈值),我们将直接计算矩阵相乘的结果。否则,我们将任务分解为四个子任务,并将这些子任务分配给其他线程。

} else {int rowMid = (rowStart + rowEnd) / 2;int colMid = (colStart + colEnd) / 2;MatrixMultiplicationTask task11 = new MatrixMultiplicationTask(A, B, rowStart, rowMid, colStart, colMid);MatrixMultiplicationTask task12 = new MatrixMultiplicationTask(A, B, rowStart, rowMid, colMid, colEnd);MatrixMultiplicationTask task21 = new MatrixMultiplicationTask(A, B, rowMid, rowEnd, colStart, colMid);MatrixMultiplicationTask task22 = new MatrixMultiplicationTask(A, B, rowMid, rowEnd, colMid, colEnd);invokeAll(task11, task12, task21, task22);double[][] C11 = task11.join();double[][] C12 = task12.join();double[][] C21 = task21.join();double[][] C22 = task22.join();return combineMatrices(C11, C12, C21, C22);private double[][] combineMatrices(double[][] C11, double[][] C12, double[][] C21, double[][] C22) {int numRows = C11.length + C21.length;int numCols = C11[0].length + C12[0].length;double[][] C = new double[numRows][numCols];for (int i = 0; i < numRows; i++) {for (int j = 0; j < numCols; j++) {if (i < C11.length && j < C11[0].length) {C[i][j] = C11[i][j];} else if (i < C11.length) {C[i][j] = C12[i][j - C11[0].length];} else if (j < C11[0].length) {C[i][j] = C21[i - C11.length][j];} else {C[i][j] = C22[i - C11.length][j - C11[0].length];return C;

我们将子任务的结果合并为一个完整的结果矩阵。现在,我们已经实现了一个并行的矩阵相乘任务,可以使用 `ForkJoinPool` 来执行它:

public static void main(String[] args) {double[][] A = generateRandomMatrix(1024, 1024);double[][] B = generateRandomMatrix(1024, 1024);ForkJoinPool pool = new ForkJoinPool();MatrixMultiplicationTask task = new MatrixMultiplicationTask(A, B, 0, A.length, 0, B[0].length);double[][] C = pool.invoke(task);// Do something with the result matrix C5 Fork/Join 框架的优化策略

在使用 Fork/Join 框架时,可以通过应用一些优化策略来提高性能和资源利用率。以下是一些常见的优化策略:

 

  1. 设置合适的阈值:合适的阈值可以平衡任务分解和计算的开销。阈值过大可能导致任务之间的负载不均衡,而阈值过小可能导致过多的任务创建和管理开销。通常,可以通过实验和性能分析来确定合适的阈值。
  2. 避免任务窃取的开销:任务窃取是 Fork/Join 框架的核心特性之一。当一个线程的任务队列为空时,它会尝试从其他线程的任务队列中“窃取”任务。为了减少任务窃取的开销,可以尝试将相关任务分组,以便它们可以在同一个线程中顺序执行。
  3. 充分利用计算资源:合理设置线程池的大小以充分利用处理器资源。通常,线程池的大小应该接近于可用处理器的数量。可以使用 Runtime.getRuntime().availableProcessors() 方法来查询可用处理器的数量。
  4. 使用 ForkJoinTask.invokeAll() 方法:当有多个子任务需要执行时,可以使用 ForkJoinTask.invokeAll() 方法来同时调度它们。这样可以减少任务管理的开销,并允许框架更有效地调度任务。
  5. 减少锁的使用:在 Fork/Join 任务中,尽量避免使用锁,因为它们可能导致线程阻塞和性能下降。可以考虑使用原子变量、并发集合和其他无锁数据结构来替换锁。
  6. 减少共享资源的争用:尽量减少任务之间对共享资源的争用。例如,可以使用局部变量来存储中间结果,而不是使用全局变量。
7 Fork/Join 框架的局限性和替代方案

 

尽管 Fork/Join 框架为我们提供了一种简单有效的方法来实现并行任务,但它并非没有局限性。在本节中,我们将讨论一些 Fork/Join 框架的局限性以及可行的替代方案。

局限性

  1. 可伸缩性:在大型系统中,当线程数量不断增加时,Fork/Join 框架的性能可能会受到限制。这是因为任务分解和结果合并可能会引入额外的开销。
  2. 负载平衡:Fork/Join 框架依赖于合适的任务分解策略来实现负载平衡。如果任务分解不均匀,某些线程可能会变得繁忙,而其他线程可能处于空闲状态,导致整体性能下降。
  3. 递归实现:Fork/Join 框架的设计是基于递归的,这可能导致栈溢出问题,特别是在处理非常大的数据集或高度嵌套的任务时。此外,递归实现通常比迭代实现更难以理解和调试。
  4. 对共享资源的竞争:在使用 Fork/Join 框架时,必须注意避免对共享资源的竞争,否则可能导致性能下降或数据不一致。确保线程安全和正确的同步策略至关重要。
替代方案
  1. Java 并行流:自 Java 8 引入了 Stream API 以来,Java 并行流(java.util.stream) 提供了一种简单且易于使用的方法来实现并行处理。并行流隐藏了底层的线程管理和任务分配,使您能够专注于实现业务逻辑。然而,并行流在某些情况下可能没有 Fork/Join 框架灵活,特别是在需要定制任务分解策略的情况下。
  2. CompletableFuture:Java 8 中引入的 CompletableFuture 提供了一种处理异步计算的方法。它允许您将多个异步任务组合在一起,以创建更复杂的异步工作流。CompletableFuture 提供了丰富的 API,可用于处理异常、超时和结果转换等。相比 Fork/Join 框架,CompletableFuture 更适用于处理 I/O 密集型任务,而不仅仅是 CPU 密集型任务。
  3. Akka:Akka 是一个基于 Actor 模型的并发和分布式计算框架,旨在简化并发编程和构建高可用、弹性的系统。Akka 允许您构建无共享状态的、高度解耦的并发系统。尽管 Akka 是一个更复杂的解决方案,但它为构建大型、分布式应用程序提供了强大的功能。
  4. RxJava:RxJava 是一个基于响应式编程范式的库,它提供了一种处理异步数据流和事件的方法。RxJava 允许您组合和转换异步操作,以实现复杂的并发逻辑。RxJava 适用于处理事件驱动的、基于消息传递的系统,并提供了丰富的操作符和功能来处理背压、错误处理和资源管理等。
  5. Project Loom:Project Loom 是一个 Java 平台的未来特性,旨在引入轻量级、高效的线程(称为纤程或协程)来简化并发编程。虽然它尚未正式发布,但 Project Loom 的目标是通过提供可伸缩、易于使用的抽象来解决现有并发框架的局限性。当 Loom 可用时,它可能成为处理大型并发任务的理想选择。

 

虽然 Fork/Join 框架具有一定的局限性,但仍然是一个非常有用的工具。然而,在某些情况下,您可能需要探索其他替代方案,以便更好地满足您的需求。

总结

看完上面的内容,你觉得自己毕业了吗。



Tags:框架   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Web Components实践:如何搭建一个框架无关的AI组件库
一、让人又爱又恨的Web ComponentsWeb Components是一种用于构建可重用的Web元素的技术。它允许开发者创建自定义的HTML元素,这些元素可以在不同的Web应用程序中重复使用,并且...【详细内容】
2024-04-03  Search: 框架  点击:(8)  评论:(0)  加入收藏
Htmx,它到底是框架还是库?
在最近的前端开发技术的探讨中,htmx经常成为热议的话题。一些人批评它,认为尽管htmx批评现代前端框架过于复杂,但它自己却似乎也是一个复杂的框架。这种看法值得我们深入思考。...【详细内容】
2024-03-28  Search: 框架  点击:(16)  评论:(0)  加入收藏
五大跨平台桌面应用开发框架:Electron、Tauri、Flutter等
一、什么是跨平台桌面应用开发框架跨平台桌面应用开发框架是一种工具或框架,它允许开发者使用一种统一的代码库或语言来创建能够在多个操作系统上运行的桌面应用程序。传统上...【详细内容】
2024-02-26  Search: 框架  点击:(47)  评论:(0)  加入收藏
Spring Security权限控制框架使用指南
在常用的后台管理系统中,通常都会有访问权限控制的需求,用于限制不同人员对于接口的访问能力,如果用户不具备指定的权限,则不能访问某些接口。本文将用 waynboot-mall 项目举例...【详细内容】
2024-02-19  Search: 框架  点击:(39)  评论:(0)  加入收藏
Go Gin框架实现优雅地重启和停止
在Web应用程序中,有时候我们需要重启或停止服务器,无论是因为更新代码还是进行例行维护。在这种情景下,我们需要保证应用程序的可用性和数据的一致性。这就需要优雅地关闭和重...【详细内容】
2024-01-30  Search: 框架  点击:(67)  评论:(0)  加入收藏
链世界:一种简单而有效的人类行为Agent模型强化学习框架
强化学习是一种机器学习的方法,它通过让智能体(Agent)与环境交互,从而学习如何选择最优的行动来最大化累积的奖励。强化学习在许多领域都有广泛的应用,例如游戏、机器人、自动驾...【详细内容】
2024-01-30  Search: 框架  点击:(68)  评论:(0)  加入收藏
OpenHarmony - 基于ArkUI框架实现日历应用
前言对于刚刚接触OpenHarmony应用开发的开发者,最快的入门方式就是开发一个简单的应用,下面记录了一个日历应用的开发过程,通过日历应用的开发,来熟悉基本图形的绘制,ArkUI的组件...【详细内容】
2024-01-16  Search: 框架  点击:(54)  评论:(0)  加入收藏
阿里“AI替换万物”框架火爆社区,网友:偶像不需要真人了?
白交 发自 凹非寺量子位 | 公众号 QbitAIReplace Anything as you want。现在只需框住你需要保留的区域,AI就可以替换万物了!比如让霉霉穿上中国旗袍,结果发饰、服装、背景等各...【详细内容】
2024-01-15  Search: 框架  点击:(66)  评论:(0)  加入收藏
分布式事务框架选择与实践
分布式事务是处理跨多个服务的原子操作的关键概念,而选择适合应用场景的框架对于确保事务一致性至关重要。以下是几个常见的分布式事务框架,并讨论它们的使用和实践。1. XA协...【详细内容】
2024-01-05  Search: 框架  点击:(96)  评论:(0)  加入收藏
JavaScript前端框架2024年展望
Angular、Next.js、React和Solid的维护者和创作者们展望2024年,分享了他们计划中的改进。译自2024 Predictions by JavaScript Frontend Framework Maintainers,作者 Loraine...【详细内容】
2024-01-05  Search: 框架  点击:(91)  评论:(0)  加入收藏
▌简易百科推荐
Web Components实践:如何搭建一个框架无关的AI组件库
一、让人又爱又恨的Web ComponentsWeb Components是一种用于构建可重用的Web元素的技术。它允许开发者创建自定义的HTML元素,这些元素可以在不同的Web应用程序中重复使用,并且...【详细内容】
2024-04-03  京东云开发者    Tags:Web Components   点击:(8)  评论:(0)  加入收藏
Kubernetes 集群 CPU 使用率只有 13% :这下大家该知道如何省钱了
作者 | THE STACK译者 | 刘雅梦策划 | Tina根据 CAST AI 对 4000 个 Kubernetes 集群的分析,Kubernetes 集群通常只使用 13% 的 CPU 和平均 20% 的内存,这表明存在严重的过度...【详细内容】
2024-03-08  InfoQ    Tags:Kubernetes   点击:(12)  评论:(0)  加入收藏
Spring Security:保障应用安全的利器
SpringSecurity作为一个功能强大的安全框架,为Java应用程序提供了全面的安全保障,包括认证、授权、防护和集成等方面。本文将介绍SpringSecurity在这些方面的特性和优势,以及它...【详细内容】
2024-02-27  风舞凋零叶    Tags:Spring Security   点击:(54)  评论:(0)  加入收藏
五大跨平台桌面应用开发框架:Electron、Tauri、Flutter等
一、什么是跨平台桌面应用开发框架跨平台桌面应用开发框架是一种工具或框架,它允许开发者使用一种统一的代码库或语言来创建能够在多个操作系统上运行的桌面应用程序。传统上...【详细内容】
2024-02-26  贝格前端工场    Tags:框架   点击:(47)  评论:(0)  加入收藏
Spring Security权限控制框架使用指南
在常用的后台管理系统中,通常都会有访问权限控制的需求,用于限制不同人员对于接口的访问能力,如果用户不具备指定的权限,则不能访问某些接口。本文将用 waynboot-mall 项目举例...【详细内容】
2024-02-19  程序员wayn  微信公众号  Tags:Spring   点击:(39)  评论:(0)  加入收藏
开发者的Kubernetes懒人指南
你可以将本文作为开发者快速了解 Kubernetes 的指南。从基础知识到更高级的主题,如 Helm Chart,以及所有这些如何影响你作为开发者。译自Kubernetes for Lazy Developers。作...【详细内容】
2024-02-01  云云众生s  微信公众号  Tags:Kubernetes   点击:(50)  评论:(0)  加入收藏
链世界:一种简单而有效的人类行为Agent模型强化学习框架
强化学习是一种机器学习的方法,它通过让智能体(Agent)与环境交互,从而学习如何选择最优的行动来最大化累积的奖励。强化学习在许多领域都有广泛的应用,例如游戏、机器人、自动驾...【详细内容】
2024-01-30  大噬元兽  微信公众号  Tags:框架   点击:(68)  评论:(0)  加入收藏
Spring实现Kafka重试Topic,真的太香了
概述Kafka的强大功能之一是每个分区都有一个Consumer的偏移值。该偏移值是消费者将读取的下一条消息的值。可以自动或手动增加该值。如果我们由于错误而无法处理消息并想重...【详细内容】
2024-01-26  HELLO程序员  微信公众号  Tags:Spring   点击:(86)  评论:(0)  加入收藏
SpringBoot如何实现缓存预热?
缓存预热是指在 Spring Boot 项目启动时,预先将数据加载到缓存系统(如 Redis)中的一种机制。那么问题来了,在 Spring Boot 项目启动之后,在什么时候?在哪里可以将数据加载到缓存系...【详细内容】
2024-01-19   Java中文社群  微信公众号  Tags:SpringBoot   点击:(86)  评论:(0)  加入收藏
花 15 分钟把 Express.js 搞明白,全栈没有那么难
Express 是老牌的 Node.js 框架,以简单和轻量著称,几行代码就可以启动一个 HTTP 服务器。市面上主流的 Node.js 框架,如 Egg.js、Nest.js 等都与 Express 息息相关。Express 框...【详细内容】
2024-01-16  程序员成功  微信公众号  Tags:Express.js   点击:(88)  评论:(0)  加入收藏
站内最新
站内热门
站内头条