看到JDK1.8中新增的WorkStealingPool
线程池,突然很好奇,这个线程池实际作用对象为ForkJoinTask
,也就是JDK1.7新增的Fork/Join框架,看了一下Fork/Join框架原理,感觉这种分而治之策略很不错,将大任务分解为一个又一个小任务,然后将结果归并。想想这种策略用在递归中不是刚好合适吗?于是写了一个快排程序,比较一下单线程与多线程速度差距到底有多大。
NonWorkStealingPoolTask.java
package cn.crabime;
import java.util.List;
/**
* 直接使用快排(单线程)进行排序
*/
public class NonWorkStealingPoolTask {
private final static int CUTOFF = 10;
public static void quickSort(int[] nums, int left, int right) {
if (left + CUTOFF < right) {
int pivot = median(nums, left, right);
int i = left, j = right;
for (;;) {
while (nums[++i] < pivot) {}
while (nums[--j] > pivot) {}
if (i < j)
swap(nums, i, j);
else
break;
}
swap(nums, i, j - 1);
quickSort(nums, left, i - 1);
quickSort(nums, i + 1, right);
} else {
int j;
for (int i = left + 1; i <= right; i++) {
int tmp = nums[i];
for (j = i; j > 0 && nums[j - 1] > tmp; j--) {
nums[j] = nums[j - 1];
}
nums[j] = tmp;
}
}
}
private static int median(int[] nums, int left, int right) {
int center = (left + right) / 2;
if (nums[left] > nums[center])
swap(nums, left, center);
if (nums[left] > nums[right])
swap(nums, left, right);
if (nums[center] > nums[right])
swap(nums, center, right);
swap(nums, center, right - 1);
return nums[right - 1];
}
private static void swap(int[] nums, int left, int right) {
int tmp = nums[left];
nums[left] = nums[right];
nums[right] = tmp;
}
public static void main(String[] args) {
List list = MillionNumberGenerator.generateNumbers(300000);
int[] num = new int[list.size()];
for (int i = 0; i < list.size(); i++) {
num[i] = list.get(i);
}
long start = System.currentTimeMillis();
quickSort(num, 0, num.length - 1);
long end = System.currentTimeMillis();
System.out.println("总共耗时:" + (end - start));
}
}
上面代码大体逻辑即当排序的数据总量小于10时,直接执行插入排序,否则进行快排,参照《数据结构与算法分析 第2版》,这样排序性能有15%提升。
WorkStealingPoolTask.java
package cn.crabime;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
/**
* 演示如何使用jdk1.8新增的WorkStealingPool
*/
public class WorkStealingPoolTask extends RecursiveAction {
private static final int CUTOFF = 10;
private int[] nums;
private int left;
private int right;
public WorkStealingPoolTask(int[] nums, int left, int right) {
this.nums = nums;
this.left = left;
this.right = right;
}
public static void insertSort(int[] nums) {
int j;
for (int i = 1; i < nums.length; i++) {
int tmp = nums[i];
for (j = i; j > 0 && tmp < nums[j - 1]; j--) {
nums[j] = nums[j - 1];
}
nums[j] = tmp;
}
}
public static void quickSort(int[] nums, int left, int right) {
if (left + CUTOFF < right) {
int pivot = median3(nums, left, right);
int i = left, j = right - 1;
for (;;) {
while (nums[++i] < pivot) {}
while (nums[--j] > pivot) {}
if (i < j) {
swap(nums, i, j);
} else {
// i >= j情况即可直接跳出循环
break;
}
}
// i此时在j右侧,将num[i]值与pivot进行互换
swap(nums, i, right - 1);
quickSort(nums, left, i - 1);
quickSort(nums, i + 1, right);
} else {
insertSort(nums);
}
}
/**
* 三数中值法获取pivot值
*/
public static int median3(int[] nums, int left, int right) {
int center = (left + right) / 2;
if (nums[left] > nums[center]) {
swap(nums, left, center);
}
if (nums[left] > nums[right]) {
swap(nums, left, right);
}
if (nums[center] > nums[right]) {
swap(nums, center, right);
}
// center与right-1位置元素进行交换
swap(nums, center, right - 1);
return nums[right - 1];
}
private static void swap(int[] nums, int left, int right) {
int tmp = nums[left];
nums[left] = nums[right];
nums[right] = tmp;
}
public static void main(String[] args) {
ForkJoinPool forkJoinPool = new ForkJoinPool();
List list = MillionNumberGenerator.generateNumbers(30000000);
int[] num = new int[list.size()];
for (int i = 0; i < list.size(); i++) {
num[i] = list.get(i);
}
WorkStealingPoolTask stealingPoolTask = new WorkStealingPoolTask(num, 0, num.length - 1);
forkJoinPool.submit(stealingPoolTask);
long start = System.currentTimeMillis();
try {
stealingPoolTask.get();
long end = System.currentTimeMillis();
System.out.println("总共耗时:" + (end - start));
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
@Override
protected void compute() {
WorkStealingPoolTask task1 = null;
WorkStealingPoolTask task2 = null;
//在左右间距小于10时,可直接采用插入排序
if (left + CUTOFF < right) {
int pivot = median3(nums, left, right);
int i = left, j = right - 1;
for (;;) {
while (nums[++i] < pivot) {}
while (nums[--j] > pivot) {}
if (i < j) {
swap(nums, i, j);
} else {
// i >= j情况即可直接跳出循环
break;
}
}
// i此时在j右侧,将num[i]值与pivot进行互换
swap(nums, i, right - 1);
task1 = new WorkStealingPoolTask(nums, left, i - 1);
task1.fork();
task2 = new WorkStealingPoolTask(nums, i + 1, right);
task2.fork();
if (!task1.isDone()) {
task1.join();
}
if (!task2.isDone()) {
task2.join();
}
} else {
insertSort(nums);
}
}
}
这里我使用的PC配置为8G内存Intel i5处理器单处理器2核Mac Air,上面NULL表示很长时间跑不出结果,很长时间表示t > 3m,后面由于电脑疯狂散热且发烫,没办法,手动终止了进程。
这里我借助了Mac三的activity软件分析进程上下文切换和CPU时钟情况,下面是对比图:
由于这里统计的是某个jvm进程信息,我们排序线程只是其中的主线程,还有一些jvm内置线程如GC线程、C1编译线程等,所以还是存在一些上下文切换的。当然我们还是看上面截图结果可以发现:Fork/Join子任务线程间上下文切换非常的频繁,是单线程的5倍;其次,CPU time远高于单线程。那么大部分CPU时间都花在了线程间上下文切换了,说明Fork/Join在我们的这次测试中是失败的,准确的说是在快速排序中不合适。