根据Wikipedia的说法, 并行性是“在多个处理器上同时执行已编程指令和数据的多个实例的某种组合”,并且Java从第1天起就有类和接口来实现此目的(有点……)。您可能将它们称为: java .lang.Thread , java.lang.Runnable等…并发实用程序( java.util.concurrent包)的作用是简化并发任务的编码方式,因此我们的代码更加简单和简洁。 作为开发人员,在具有更高处理资源的计算机上运行应用程序时,我们无需执行任何操作,显然,我们的应用程序性能会提高,但是我们是否真的在最大限度地利用处理资源? 答案是否定的。
这篇文章将向您展示在处理可分为小问题的问题时,Fork / Join框架将如何最大程度地帮助我们使用处理资源,并且解决这些小问题中的每一个的所有解决方案都将产生大问题的解决方案问题(例如递归,分而治之)。
你需要什么
NetBeans 7+或任何其他支持Java 7 JDK 7+的 IDE
图像模糊 ,来自Oracle的示例
基础
Fork / Join框架专注于使用计算机中可用的所有处理资源来提高应用程序的性能。 它旨在简化Divide and Conquer算法中的并行性。 Fork / Join框架背后的魔力在于其工作窃取算法,该算法中的工作线程可以从其他繁忙线程中自由窃取任务,因此所有线程始终都在工作。 以下是开始使用框架时应了解的基础知识:
- Fork意味着将任务分为子任务并进行处理。
- 联接意味着将每个子任务的解决方案合并为一个通用解决方案。
- java.lang.Runtime使用此类来获取可用于Java虚拟机的处理器数量。 为此,请使用方法+ availableProcessors():int 。
- java.util.concurrent.ForkJoinPool框架的主类,是实现工作窃取算法并负责运行任务的类。
- java.util.concurrent.ForkJoinTask抽象类,用于在java.util.concurrent.ForkJoinPool中运行的任务。 将任务理解为整个工作的一部分,例如,如果您需要在数组上做某事,则一个任务可以在位置0到n / 2上工作,而另一个任务可以在位置(n / 2)+1上工作到n-1 ,其中n是数组的长度。
- java.util.concurrent.RecursiveAction抽象任务类的子类,在不需要任务返回结果时使用它,例如,当任务在数组的位置上工作时,它不返回任何内容,因为它在阵列上工作。 为了完成这项工作,您应该实现的方法是compute():void ,请注意void返回值。
- java.util.concurrent.RecursiveTask抽象任务类的子类,当任务返回结果时使用它。 例如,在计算斐波那契数时,每个任务必须返回它计算出的数以加入它们并获得一般解。 为了完成这项工作,您应该实现的方法是compute():V ,其中V是返回值的类型; 对于Fibonacci示例, V可以是java.lang.Integer。
使用框架时,应定义一个标志,该标志指示是否有必要派生/加入任务或是否应直接计算工作。 例如,在处理数组时,可以指定如果数组的长度大于500_000_000,则应分叉/加入任务,否则,数组足够小以直接计算。 本质上,接下来应显示的算法如下:
if(the job is small enough)
{compute directly
}
else
{split the work in two pieces (fork)invoke the pieces and join the results (join)
}
好了,现在理论太多了,我们来看一个例子。
这个例子
模糊图像需要对图像的每个像素进行处理。 如果图像足够大,我们将需要处理大量像素,因此我们可以使用fork / join对它们进行处理,并最大限度地利用处理资源。 您可以从Java™Tutorials站点下载源代码。
下载源代码后,打开NetBeans IDE 7.x并创建一个新项目:
然后从显示的弹出窗口的Java类别中选择“具有现有源代码的Java项目” :
选择一个名称和一个项目文件夹,然后单击下一步>
现在, 在图像示例中选择下载了Blur源代码的文件夹:
并选择文件ForkBlur.java,然后单击完成:
将导入源代码,并创建一个新项目。 请注意,新项目显示为错误,这是因为默认情况下未启用Java 7:
要解决此问题,请右键单击项目名称,然后选择选项属性 。 在弹出对话框中,转到“ 库”,然后从“ Java平台组合框”中选择“ JDK 1.7 ”:
现在,转到选项Sources并从Source / Binary Format ComboBox中选择JDK 7 :
最后但并非最不重要的一点是,在运行此应用程序时增加分配给虚拟机的内存,因为我们将访问500万个位置数组(或更多)。 转到选项运行并在VM Options TextBox上插入-Xms1024m -Xmx1024m :
单击确定 ,您的项目应该没有错误进行编译。 现在,我们需要找到足够大的图像,以便可以处理较大的阵列。 一段时间后,由于好奇心机器人的帮助,我发现了火星上的一些很棒的图像(约150 MB),您可以从此处下载图像 。 下载图像后,将其粘贴到项目的文件夹中。
在运行示例之前,我们需要修改源代码,以便控制何时使用Fork / Join框架运行它。 在ForkBlur.java文件中,转到第104行,以更改将要使用的图像的名称:
//Change for the name of the image you pasted
//on the project's folder.
String filename = 'red-tulips.jpg';
然后,用下面的代码替换第130至136行:
ForkBlur fb = new ForkBlur(src, 0, src.length, dst);boolean computeDirectly = true;long startTime = System.currentTimeMillis();if (computeDirectly) {fb.computeDirectly();} else {ForkJoinPool pool = new ForkJoinPool();pool.invoke(fb);}long endTime = System.currentTimeMillis();
注意computeDirectly标志。 为true时 ,我们将不使用fork / Join Framework,而是直接计算任务。 如果为false,将使用fork / join框架。
ForkBlur类中的compute():void方法实现了fork / join算法。 它基于数组的长度,当数组的长度大于10_000时,将分派任务,否则将直接计算任务。
在不使用Fork / Join框架( computeDirectly = true )的情况下, 在图像示例上执行Blur时,您可以看到我的2个处理器,大约花了14 秒钟完成工作:
您可以看到处理器正在工作,但没有达到最大。 当使用Fork / Join框架( computeDirectly = false )时,您可以看到它们以100%的速度工作,并且花了将近50%的时间来完成工作:
该视频显示了完整的过程:
希望您能看到这个框架有多有用。 当然,您不能在代码中全部使用它,但是只要您有一个可以分为多个小任务的任务,那么您就知道该呼叫谁。
参考: Java 7:在Java and ME博客上, 与 JCG合作伙伴 Alexis Lopez交流Fork / Join框架 。
翻译自: https://www.javacodegeeks.com/2012/10/java-7-meet-forkjoin-framework.html