叉叉框架_叉/连接框架

叉叉框架

本文是我们名为Java Concurrency Essentials的学院课程的一部分。

在本课程中,您将深入探讨并发的魔力。 将向您介绍并发和并发代码的基础知识,并学习诸如原子性,同步和线程安全性的概念。 在这里查看 !

目录

1.简介 2.叉/连接
2.1。 递归任务 2.2。 递归动作 2.3。 ForkJoinPool和ExecutorService

1.简介

本文介绍了Fork / Join框架,该框架从1.7版开始就是JDK的一部分。 它描述了框架的基本功能,并提供了一些示例以提供一些实践经验。

2.叉/连接

Fork / Join框架的基类是java.util.concurrent.ForkJoinPool 。 此类实现ExecutorExecutorService这两个接口,并AbstractExecutorService 。 因此, ForkJoinPool基本上是一个线程池,它承担特殊任务,即ForkJoinTask 。 此类实现已知的Future接口以及诸如get()cancel()isDone() 。 除此之外,该类还提供了两个为整个框架命名的方法: fork()join()

调用fork()将启动任务的异步执行时,调用join()将等待直到任务完成并检索其结果。 因此,我们可以将给定任务拆分为多个较小的任务,分叉每个任务,最后等待所有任务完成。 这使复杂问题的实现更加容易。

在计算机科学中,这种方法也称为分治法。 每当一个问题太复杂而无法立即解决时,它就会分为多个较小的问题,并且更容易解决。 可以这样写成伪代码:

if(problem.getSize() > THRESHOLD) {SmallerProblem smallerProblem1 = new SmallerProblem();smallerProblem1.fork();SmallerProblem smallerProblem2 = new SmallerProblem();smallerProblem2.fork();return problem.solve(smallerProblem1.join(), smallerProblem2.join());
} else {return problem.solve();
}

首先,我们检查问题的当前大小是否大于给定的阈值。 在这种情况下,我们将问题分成较小的问题,对每个新任务进行fork() ,然后通过调用join()等待结果。 当join()返回每个子任务的结果时,我们必须找到较小问题的最佳解决方案,并将其作为最佳解决方案返回。 重复这些步骤,直到给定的阈值太低并且问题很小,我们可以直接计算其解而无需进一步除法。

递归任务

为了更好地掌握此过程,我们实现了一种算法,该算法可在整数值数组中找到最小的数字。 这个问题不是您使用ForkJoinPool在日常工作中解决的问题,但是以下实现非常清楚地显示了基本原理。 在main()方法中,我们设置了一个带有随机值的整数数组,并创建了一个新的ForkJoinPool

传递给其构造函数的第一个参数是所需并行度的指示器。 在这里,我们在Runtime查询可用的CPU内核数。 然后,我们调用invoke()方法并传递FindMin的实例。 FindMin扩展了RecursiveTask类,该类本身是前面提到的ForkJoinTask的子类。 类ForkJoinTask实际上有两个子类:一个子类用于返回值的任务( RecursiveTask ),另一个子类用于不返回值的任务( RecursiveAction )。 超类迫使我们实现compute() 。 在这里,我们看一下整数数组的给定切片,并确定当前问题是否太大而无法立即解决。

当在数组中找到最小的数时,要直接解决的最小问题大小是将两个元素相互比较并返回它们的最小值。 如果当前有两个以上的元素,则将数组分为两部分,然后再在这两个部分中找到最小的数字。 通过创建两个新的FindMin实例来完成此操作。

构造函数被提供给数组以及开始和结束索引。 然后,我们通过调用fork()异步开始执行这两个任务。 该调用将两个任务提交到线程池的队列中。 线程池实现了一种称为工作窃取的策略,即,如果所有其他线程都有足够的工作要做,则当前线程会从其他任务之一中窃取其工作。 这样可以确保任务尽快执行。

public class FindMin extends RecursiveTask<Integer> {private static final long serialVersionUID = 1L;private int[] numbers;private int startIndex;private int endIndex;public FindMin(int[] numbers, int startIndex, int endIndex) {this.numbers = numbers;this.startIndex = startIndex;this.endIndex = endIndex;}@Overrideprotected Integer compute() {int sliceLength = (endIndex - startIndex) + 1;if (sliceLength > 2) {FindMin lowerFindMin = new FindMin(numbers, startIndex, startIndex + (sliceLength / 2) - 1);lowerFindMin.fork();FindMin upperFindMin = new FindMin(numbers, startIndex + (sliceLength / 2), endIndex);upperFindMin.fork();return Math.min(lowerFindMin.join(), upperFindMin.join());} else {return Math.min(numbers[startIndex], numbers[endIndex]);}}public static void main(String[] args) {int[] numbers = new int[100];Random random = new Random(System.currentTimeMillis());for (int i = 0; i < numbers.length; i++) {numbers[i] = random.nextInt(100);}ForkJoinPool pool = new ForkJoinPool(Runtime.getRuntime().availableProcessors());Integer min = pool.invoke(new FindMin(numbers, 0, numbers.length - 1));System.out.println(min);}
}

递归动作

正如上面在RecursiveTask旁边提到的,我们还有RecursiveAction类。 与RecursiveTask相比,它不必返回值,因此可以将其用于可以直接在给定数据结构上执行的异步计算。 这样的例子是从彩色图像中计算出灰度图像。 我们要做的就是遍历图像的每个像素,并使用以下公式从RGB值中计算灰度值:

gray = 0.2126 * red + 0.7152 * green + 0.0722 * blue

浮点数表示特定颜色对我们人类对灰色的感知做出的贡献。 由于最高值用于绿色,因此可以得出结论,灰度图像仅被计算为绿色部分的近3/4。 因此,假设图像是代表实际像素数据的对象,并且使用setRGB()getRGB()方法检索实际RGB值,则基本实现将如下所示:

for (int row = 0; row < height; row++) {for (int column = 0; column < bufferedImage.getWidth(); column++) {int grayscale = computeGrayscale(image.getRGB(column, row));image.setRGB(column, row, grayscale);}
}

上面的实现在单个CPU机器上运行良好。 但是,如果我们有多个CPU可用,我们可能希望将此工作分配给可用的内核。 因此,我们可以使用ForkJoinPool并为图像的每一行(或每一列)提交一个新任务,而不是遍历所有像素的两个嵌套for循环。 一旦将一行转换为灰度,当前线程就可以在另一行上工作。

以下示例实现了此原理:

public class GrayscaleImageAction extends RecursiveAction {private static final long serialVersionUID = 1L;private int row;private BufferedImage bufferedImage;public GrayscaleImageAction(int row, BufferedImage bufferedImage) {this.row = row;this.bufferedImage = bufferedImage;}@Overrideprotected void compute() {for (int column = 0; column < bufferedImage.getWidth(); column++) {int rgb = bufferedImage.getRGB(column, row);int r = (rgb >> 16) & 0xFF;int g = (rgb >> 8) & 0xFF;int b = (rgb & 0xFF);int gray = (int) (0.2126 * (float) r + 0.7152 * (float) g + 0.0722 * (float) b);gray = (gray << 16) + (gray << 8) + gray;bufferedImage.setRGB(column, row, gray);}}public static void main(String[] args) throws IOException {ForkJoinPool pool = new ForkJoinPool(Runtime.getRuntime().availableProcessors());BufferedImage bufferedImage = ImageIO.read(new File(args[0]));for (int row = 0; row < bufferedImage.getHeight(); row++) {GrayscaleImageAction action = new GrayscaleImageAction(row, bufferedImage);pool.execute(action);}pool.shutdown();ImageIO.write(bufferedImage, "jpg", new File(args[1]));}
}

在main()方法中,我们使用Java的ImageIO类读取图像。 返回的BufferedImage实例具有我们需要的所有方法。 我们可以查询行数和列数,并检索和设置每个像素的RGB值。 因此,我们要做的是遍历所有行并将新的GrayscaleImageAction提交到我们的ForkJoinPool 。 后者已收到有关可用处理器的提示,作为其构造函数的参数。

现在, ForkJoinPool通过调用其compute()方法来异步启动任务。 在此方法中,我们遍历每行并通过其灰度值更新相应的RGB值。 将所有任务提交给池后,我们在主线程中等待整个池的关闭,然后使用ImageIO.write()方法将更新的BufferedImage写回到磁盘。

令人惊讶的是,与不使用可用处理器的情况相比,我们只需要多几行代码即可。 这再次显示了使用java.util.concurrent包的可用资源可以节省多少工作。

ForkJoinPool提供了三种不同的提交任务的方法:

  • execute(ForkJoinTask) :此方法异步执行给定的任务。 它没有返回值。
  • invoke(ForkJoinTask) :此方法等待任务返回值。
  • submit(ForkJoinTask) :此方法异步执行给定的任务。 它返回对任务本身的引用。 因此,任务引用可用于查询结果(因为它实现了Future接口)。

有了这些知识,很清楚为什么我们要使用execute()方法提交上述GrayscaleImageAction 。 如果我们改为使用invoke() ,则主线程将等待任务完成,而我们将不会利用可用的并行度。

仔细研究ForkJoinTask-API,我们会发现相同的区别:

  • ForkJoinTask.fork()ForkJoinTask是异步执行的。 它没有返回值。
  • ForkJoinTask.invoke() :立即执行ForkJoinTask并在完成后返回结果。

ForkJoinPool和ExecutorService

既然我们知道ExecutorServiceForkJoinPool ,您可能会问自己为什么我们应该使用ForkJoinPool而不是ExecutorService 。 两者之间的差异不是很大。 两者都具有execute()submit()方法,并采用一些常见接口的实例,例如RunnableCallableRecursiveActionRecursiveTask

为了更好地理解这些区别,让我们尝试使用ExecutorService从上面实现FindMin类:

public class FindMinTask implements Callable<Integer> {private int[] numbers;private int startIndex;private int endIndex;private ExecutorService executorService;public FindMinTask(ExecutorService executorService, int[] numbers, int startIndex, int endIndex) {this.executorService = executorService;this.numbers = numbers;this.startIndex = startIndex;this.endIndex = endIndex;}public Integer call() throws Exception {int sliceLength = (endIndex - startIndex) + 1;if (sliceLength > 2) {FindMinTask lowerFindMin = new FindMinTask(executorService, numbers, startIndex, startIndex + (sliceLength / 2) - 1);Future<Integer> futureLowerFindMin = executorService.submit(lowerFindMin);FindMinTask upperFindMin = new FindMinTask(executorService, numbers, startIndex + (sliceLength / 2), endIndex);Future<Integer> futureUpperFindMin = executorService.submit(upperFindMin);return Math.min(futureLowerFindMin.get(), futureUpperFindMin.get());} else {return Math.min(numbers[startIndex], numbers[endIndex]);}}public static void main(String[] args) throws InterruptedException, ExecutionException {int[] numbers = new int[100];Random random = new Random(System.currentTimeMillis());for (int i = 0; i < numbers.length; i++) {numbers[i] = random.nextInt(100);}ExecutorService executorService = Executors.newFixedThreadPool(64);Future<Integer> futureResult = executorService.submit(new FindMinTask(executorService, numbers, 0, numbers.length-1));System.out.println(futureResult.get());executorService.shutdown();}
}

该代码看起来非常相似,期望我们submit()任务submit()ExecutorService ,然后使用返回的Future实例来wait()结果。 两种实现之间的主要区别可以在构造线程池的那一点上找到。 在上面的示例中,我们创建了一个具有64(!)个线程的固定线程池。 为什么选择这么大的数字? 原因是,对每个返回的Future调用get()阻塞当前线程,直到结果可用为止。 如果我们仅向可用池提供尽可能多的线程,则程序将耗尽资源并无限期地挂起。

ForkJoinPool实现了已经提到的工作窃取策略,即每次运行线程必须等待某些结果时; 该线程从工作队列中删除当前任务,并执行其他准备运行的任务。 这样,当前线程不会被阻塞,并且可以用来执行其他任务。 一旦计算出最初暂停的任务的结果,该任务就会再次执行,join()方法将返回结果。 这与普通的ExecutorService有一个重要的区别,在常规ExecutorService ,您必须在等待结果时阻止当前线程。

翻译自: https://www.javacodegeeks.com/2015/09/forkjoin-framework.html

叉叉框架

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

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

相关文章

C++ 浮点数精度判定

点击蓝字关注我们来源于网络&#xff0c;侵删一、引例看下下面这段代码&#xff0c;会输出什么结果呢&#xff1f;double x 0;for (int i 0; i < 10; i) {x 0.1;}printf("%d\n", x 1);输出如下&#xff1a;0引起这种反差的原因就是浮点误差&#xff0c;浮点数…

字节流和字符流哪个不刷新_不喜欢节流吗?

字节流和字符流哪个不刷新您别无选择–底层系统&#xff08;此处的JVM将为您完成选择&#xff09;。 我仍然记得2013年夏天&#xff0c;当时我正在运行一个项目&#xff0c;整个应用程序中只有1个URL使服务器瘫痪。 问题很简单-机器人决定以很高的速率索引我们的网站&#xff…

C/C++动态内存管理—(new与malloc)

点击蓝字关注我们来源于网络&#xff0c;侵删1.C/C内存分布虚拟地址空间分布&#xff1a;由C/C编译的程序占用的内存分为以下几个部分&#xff1a;栈区&#xff08;stack&#xff09;— 由编译器自动分配释放 &#xff0c;存放为运行函数而分配的局部变量、函数参数、返回数据、…

python实现简单小游戏_python实现简单井字棋小游戏

#Tic-Tac-Toe 井字棋游戏#全局常量X"X"O"O"EMPTY" "#询问是否继续def ask_yes_no(question):responseNone;while response not in("y","n"):responseinput(question).lower()return response#输入位置数字def ask_number(qu…

C++ sort()排序详解

点击蓝字关注我们来源自网络&#xff0c;侵删一.sort()简介1.为什么选择使用sort()我们经常会碰到排序的问题&#xff0c;如果我们不使用一些排序的方法那我们只能手撕排序&#xff0c;这样就会浪费一些时间。而且我们还需要根据需要去选择相关的排序方法&#xff1a;冒泡排序、…

java尾行注释有什么不好_注释不好吗?

java尾行注释有什么不好那天&#xff0c;我在有关Spring XML与注释的文章中运用了自己的原则&#xff0c;轻松进入了这个主题。 对于目前正在编写此新应用程序的团队来说&#xff0c;这种简单的输入方式也是我不使事情复杂化的方式&#xff0c;该应用程序的生产寿命可能为3-5年…

python输出结果为none_python的reverse函数翻转结果为None的问题

今天刷二级题的时候&#xff0c;遇到一个问题>>> L2[1,2,3,4]>>> L3L2.reverse()>>> print( L3)None>>> print(L3)None>>> print(L2.reverse())None其实我想让它输出[4,3,2,1]reverse函数&#xff0c;翻转列表然后我改了一下>…

性能测试流程_流性能

性能测试流程当我阅读Angelika Langer的Java性能教程时-Java 8流有多快&#xff1f; 我简直不敢相信&#xff0c;对于一个特定的操作&#xff0c;它们花费的时间比循环要长15倍。 流媒体性能真的会那么糟糕吗&#xff1f; 我必须找出答案&#xff01; 巧合的是&#xff0c;我最…

C++vector用法总结

点击蓝字关注我们来源自网络&#xff0c;侵删一.vector1. vector 说明1&#xff09;vector是C标准模板库中的部分内容&#xff0c;它是一个多功能的&#xff0c;能够操作多种数据结构和算法的模板类和函数库。2.&#xff09;vector之所以被认为是一个容器&#xff0c;是因为它能…

python中创建集合的语句_Python 集合(set) 介绍

集合 set集合是可变的容器集合内的数据对象都是唯一的(不能重复多次的)集合是无序的存储结构&#xff0c;集合中的数据没有先后关系集合内的元素必须是不可变对象集合是可迭代对象(可以用for等遍历)集合是相当于只有键&#xff0c;没有值的字典(键则是集合的数据)创建空集合&am…

C++ STL 线性容器的用法

点击蓝字关注我们来源于网络&#xff0c;侵删1.vectorvector 是顺序容器的一种&#xff0c;是可变长的动态数组&#xff0c;支持随机访问迭代器&#xff0c;所有stl算法都能对 vector 进行操作。vector 容器在实现时&#xff0c;动态分配的存储空间一般都大于存放元素所需的空间…

redis复制_Redis复制

redis复制本文是我们学院课程的一部分&#xff0c;标题为Redis NoSQL键值存储 。 这是Redis的速成课程。 您将学习如何安装Redis和启动服务器。 此外&#xff0c;您还会在Redis命令行上乱七八糟。 接下来是更高级的主题&#xff0c;例如复制&#xff0c;分片和集群&#xff0c…

python打开.data_DataX初体验-python命令启动以及纯java启动

一、DataX安装官网描述很详细&#xff0c;很简单---->DataX安装二、使用示例&#xff1a;从Oracle数据库导数据到Mysql数据库事先准备&#xff1a;Oracle数据库的ORDER_INFO表&#xff0c;MySQL数据库的order_info表&#xff0c;表结构相同2.1使用Python启动生成模板文件打开…

java高级教程_高级Java教程

java高级教程课程大纲 学习Java基础很容易。 但是&#xff0c;真正钻研该语言并研究其更高级的概念和细微差别将使您成为一名出色的Java开发人员。 网络上充斥着“软”&#xff0c;“便宜”&#xff0c;“低端” Java教程&#xff0c;但是所缺少的实际上是将您带入新的高度的材…

JavaWeb笔记之WEB项目

一. 版本控制 版本控制是指对软件开发过程中各种程序代码、配置文件及说明文档等文件变更的管理&#xff0c;是软件配置管理的核心思想之一。 版本控制最主要的功能就是追踪文件的变更。它将什么时候、什么人更改了文件的什么内容等信息忠实地了记录下来。每一次文件的改变&a…

c++获取数组长度

点击蓝字关注我们来源于网络&#xff0c;侵删方法一&#xff1a; 用宏函数 #define#define foo(arr) sizeof(arr)/sizeof(arr[0])int main(){int arr[4] {1,2,3,4};cout<<foo(arr)<<endl; }方法二&#xff1a;用函数模板int getArrLen1(int *a ){return sizeof(a)…

python从列表随机取出多个数据_【python】从数组随机取数据

在神经网络中&#xff0c;经常会用到批量样本训练。我们需要从数组随机取数据&#xff0c;主要有以下几种方法&#xff1a;1、np.random.shuffle&#xff1a;将原数组打乱import numpy as nparray np.random.randint(1,100,size10)#[63 32 80 33 61 45 28 55 39 80]batch_size…

java代理模式_Java代理

java代理模式本文是我们名为“ 高级Java ”的学院课程的一部分。 本课程旨在帮助您最有效地使用Java。 它讨论了高级主题&#xff0c;包括对象创建&#xff0c;并发&#xff0c;序列化&#xff0c;反射等。 它将指导您完成Java掌握的旅程&#xff01; 在这里查看 &#xff01;…

如何用python抢课_名额不够,技术来凑,利用Python实现教务系统强制性抢课

这个不是一个点击脚本&#xff0c;而是属于扩容性质的脚本。名额不够咱们利用技术来解决&#xff01;最近一学期一次的抢课大戏又来了&#xff0c;几家欢乐几家愁。O(∩_∩)O哈哈~(l我每次一选就过了hah&#xff0c;我还是有欧的时候滴)。看着他们盯着教务系统就着急&#xff0…

C++ 利用硬件加速矩阵乘法

点击蓝字关注我们来源于网络&#xff0c;侵删1.矩阵乘法定义2.矩阵类封装我们用 C封装了一个n m 的矩阵类&#xff0c;用二维数组来存储数据&#xff0c;定义如下&#xff1a;#define MAXN 1000 #define LL __int64class Matrix { private:int n, m;LL** pkData; public:Matri…