三、Fork-Join机制介绍
Stream之所以可以使用并行运算,之所以能够最大程度的使用CPU资源,是因为他的Fork-Join编程模型,在本章中我们介绍一下Fork-Join的使用,希望读者能掌握在Java中如何使用Fork-Join这一编程模型,本章内容大致如下
v Fork-Join简单使用
v 快速排序算法
v Fork-Join并行快速排序
3.1 Fork-Join简单实用
在开始说Fork-Join的API以及原理之前,我们先来看一个非常简单的例子,让读者能够快速上手,我们在本章中我们实现一个非常简单的算法,就是计算自然数的累加结果,从start到end,然后将结果输出出来即可,这个简单的再也不能简单了。
我们用传统方法实现一个,大家看一下
@Test public void testTradition() { long startTime = System.currentTimeMillis(); int start = 0; int end = 1500000; int sum = 0; for(int i = start;i<end;i++) { sum+=i; } System.out.println("Tradition Result:"+sum+",speed time:"+(System.currentTimeMillis()-startTime)); } |
好了,不想多说了,我们看一下,如何使用Fork-Join来实现这样的需求
package com.wangwenjun.forkjoin;
import java.util.concurrent.RecursiveTask;
/** * Created by wangwenjun on 2015/8/8. */ public class ConcurrencyCalculator extends RecursiveTask<Integer> {
private final int start;
private final int end;
private final static int THRESHOLD = 5;
public ConcurrencyCalculator(int start, int end) { this.start = start; this.end = end; }
@Override protected Integer compute() { int result = 0; if ((end -start) < THRESHOLD) { for (int x = start; x < end; x++) { result += x; } } else { int middle = (start + end) / 2; ConcurrencyCalculator leftCalculator = new ConcurrencyCalculator(start, middle); ConcurrencyCalculator rightCalculator = new ConcurrencyCalculator(middle, end); leftCalculator.fork(); rightCalculator.fork(); result += (leftCalculator.join() + rightCalculator.join()); } return result; } } |
如果你对上面的代码理解上有些困难,别担心,慢慢的介绍了Fork-Join和API的使用之后,你就会明白如何使用了。
3.2 Fork-Join原理以及API介绍
3.2.1 Fork-Join原理介绍
我还是不太喜欢抄袭太多别人的东西来说他的基本原理,我们来举个简单的例子吧,假设你要到数据库中查询一百万条数据,数据查询结束之后,如要对每条数据进行一些额外的操作,操作有可能很简单,有可能很复杂,如果你用单线程的方式处理势必很慢,如果你用多线程的方式处理,你需要对这100万条数据进行拆分,那些数据交给那个线程去操作,等所有的线程操作结束之后然后在集中将结果合并,说白了就是采用分而治之的办法,Fork-Join模型一言蔽之就是采用分而治之的方式,将一个任务分解成若干个子问题,子问题还可以继续拆分,如下图所示:
Doug Lea大神开发的Fork/Join并行计算框架,集成到了JDK 7中,很优雅的让你远离了自己进行加锁,释放锁,隐藏了工作线程,线程的调度等,所以你才能看到上面例子中用了很少的代码就可以模拟一个并行运算的例子。
3.2.2 Fork-Join API的介绍
Fork-Join 主要的API大致如下所示
如果你计算的需要返回值就继承RecursiveTask抽线类,如果不需要返回值就继承RecursiveAction抽象类。
看到这里关于API的介绍和基本原理的介绍,你再回头看看上面的例子,相信就不难理解了吧,在接下来我们再来实现一个比较复杂的快速排序采用并行的方式进行,加深理解。
3.3 快速排序算法
快速排序算法是冒泡算法的改进版本,正如他的名字一样,排序速度是相当的快,快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
不想浪费太多的笔墨介绍快速排序算法的原理,相信很多人,尤其是IT行业的从业者,对于写出快速排序算法这样的基本功都是信手拈来的事情,我在这里只是将我的实现代码粘贴出来。
package com.wangwenjun.forkjoin;
import java.util.Random;
/** * Created by wangwenjun on 2015/8/9. */ public class QuickSort {
private final int LEN = 10;
public int[] prepareForData() { Random random = new Random(System.currentTimeMillis()); int[] originalArray = new int[LEN]; for (int i = 0; i < LEN; i++) { originalArray[i] = random.nextInt(20); }
return originalArray; }
public void print(String literal, int[] array) { System.out.println(literal); for (int element : array) { System.out.print(element + " "); } System.out.println(); }
public void quickSort(int[] originalArray) { if (originalArray.length > 0) { sort(originalArray, 0, originalArray.length - 1); } }
private void sort(int[] originalArray, int low, int high) { if (low < high) { int middle = partition(originalArray, low, high); sort(originalArray, low, middle - 1); sort(originalArray, middle + 1, high); } }
private int partition(int[] array, int low, int high) { int tmp = array[low]; while (low < high) { while (low < high && array[high] >= tmp) { high--; } array[low] = array[high]; while (low < high && array[low] <= tmp) { low++; } array[high] = array[low]; } array[low] = tmp; return low; } } |
写个测试代码,测试一下,代码如下所示:
package com.wangwenjun.forkjoin;
import org.junit.Test;
/** * Created by wangwenjun on 2015/8/9. */ public class QuickSortTest {
/** * original literal */ private final static String ORIGINAL_LITERAL = "====The original array as below====";
/** * the array after sort. */ private final static String SORTED_LITERAL = "====The array after sorted as below====";
@Test public void testQuickSort() { QuickSort quickSort = new QuickSort(); int[] originalArray = quickSort.prepareForData(); quickSort.print(ORIGINAL_LITERAL, originalArray); quickSort.quickSort(originalArray); quickSort.print(SORTED_LITERAL, originalArray); } } |
我运行了一次,大致结果如下所示
====The original array as below==== 12 18 12 18 2 1 7 9 14 13 ====The array after sorted as below==== 1 2 7 9 12 12 13 14 18 18 |
3.4 使用Fork-Join实现并行快速排序算法
我们在采用Fork-Join的方式来实现一下,看看如何使用并行的方式提高计算能力,提升性能。
代码如下所示:
package com.wangwenjun.forkjoin;
import java.util.List; import java.util.concurrent.RecursiveAction;
/** * Created by wangwenjun on 2015/8/8. */ public class ForkJoinQuickSort<T extends Comparable> extends RecursiveAction {
private List<T> data; private int left; private int right;
public ForkJoinQuickSort(List<T> data) { this.data = data; this.left = 0; this.right = data.size() - 1; }
public ForkJoinQuickSort(List<T> data, int left, int right) { this.data = data; this.left = left; this.right = right; }
@Override protected void compute() { if (left < right) { int pivotIndex = left + ((right - left) / 2);
pivotIndex = partition(pivotIndex);
invokeAll(new ForkJoinQuickSort(data, left, pivotIndex - 1), new ForkJoinQuickSort(data, pivotIndex + 1, right)); }
}
private int partition(int pivotIndex) { T pivotValue = data.get(pivotIndex);
swap(pivotIndex, right);
int storeIndex = left; for (int i = left; i < right; i++) { if (data.get(i).compareTo(pivotValue) < 0) { swap(i, storeIndex); storeIndex++; } }
swap(storeIndex, right);
return storeIndex; }
private void swap(int i, int j) { if (i != j) { T iValue = data.get(i);
data.set(i, data.get(j)); data.set(j, iValue); } } } |
测试代码如下所示:
package com.wangwenjun.forkjoin;
import org.junit.Test;
import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.ForkJoinPool;
/** * Created by wangwenjun on 2015/8/9. */ public class ForkJoinQuickSortTest {
private static final int LEN = 10;
private List<Integer> prepareForData() { Random random = new Random(System.currentTimeMillis()); List<Integer> data = new ArrayList<>(); for (int i = 0; i < LEN; i++) { data.add(random.nextInt(20)); }
return data; }
private void print(String literal, List<Integer> data) { System.out.println(literal); for (int element : data) { System.out.print(element + " "); } System.out.println(); }
@Test public void test() { List<Integer> data = prepareForData(); print("####before", data); ForkJoinQuickSort<Integer> quickSort = new ForkJoinQuickSort<>(data);
ForkJoinPool pool = new ForkJoinPool(); pool.invoke(quickSort); print("####after", data); }
} |
运行结果如下所示:
####before 19 4 9 2 9 14 18 5 6 18 ####after 2 4 5 6 9 9 14 18 18 19 |
3.5 Fork-Join总结
Fork-Join编程模型出来的时间其实已经不算晚了,在Java 1.7版本中才被引入,做Unix C++开发的人早都掌握该项技能了,好饭不怕晚,在我们平时的工作中他还有很多的应用场景,比如你的任务很适合进行拆分,并且比较容易进行合并,提高程序的运行速度,但是我个人建议不能将获取资源的地方使用Fork,比如你要去网络读数据或者从数据库中读取数据,分开多个任务会导致网络以及数据库的压力,将处理过程Fork是一个不错的选择,获取数据除非特别需要,否则不要使用Fork增加并行,对资源提供者也会是一个不小的压力。
完整文档的下载地址为:http://download.csdn.net/detail/wangwenjun69/8981633