一、Fork/Join的同步用法同时演示返回结果值
我们先创建一组模拟业务的类,如下:
/**
*产生一组整形数组,模拟统计业务
*/
public class TestArray {
//数组长度
public static final int ARRAY_LENGTH = 3000;
public static int[] testArray() {
//随机数发生器
Random r = new Random();
int[] result = new int[ARRAY_LENGTH];
for(int i=0;i<ARRAY_LENGTH;i++){
//用数组长度*4倍以内的随机数填充数组,倍数是随意的,无任何讲究
result[i] = r.nextInt(ARRAY_LENGTH*4);
}
return result;
}
}
/**
* 我们模拟一下不用ForkJoin框架,普通同步方式去处理我们的业务
*/
public class SumNormal {
public static void main(String[] args) {
int total = 0;
int[] src = TestArray.testArray();
long start = System.currentTimeMillis();
for(int i= 0;i<src.length;i++){
//模拟处理业务时间,1毫秒,设现在我们MakeArray中有3000数据(注:这里我们先不模拟业务操作)
//SleepTools.ms(1);
total= total+ src[i];
}
System.out.println("The totalis "+total
+" spend time:"+(System.currentTimeMillis()-start)+"ms");
}
}
执行时长:
删去SleepTools.ms(1)中的注释:,总时长为5319毫秒
SleepTools工具类代码:
/**
*
*类说明:线程休眠辅助工具类
*/
public class SleepTools {
/**
* 按秒休眠
* @param seconds 秒数
*/
public static final void second(int seconds) {
try {
TimeUnit.SECONDS.sleep(seconds);
} catch (InterruptedException e) {
}
}
/**
* 按毫秒数休眠
* @param seconds 毫秒数
*/
public static final void ms(int seconds) {
try {
TimeUnit.MILLISECONDS.sleep(seconds);
} catch (InterruptedException e) {
}
}
}
使用ForkJoin(注:我们这里继承的类为RecursiveTask:可以获取返回值,可以自定义返回类型,这里我设为Integer):
public class SumArray {
private static class SumTask extends RecursiveTask<Integer>{
private final static int THRESHOLD = TestArray.ARRAY_LENGTH/10;//表示我们切成十份
private int[] src; //表示我们要实际统计的数组
private int fromIndex;//开始统计的下标
private int toIndex;//统计到哪里结束的下标
public SumTask(int[] src, int fromIndex, int toIndex) {
this.src = src;
this.fromIndex = fromIndex;
this.toIndex = toIndex;
}
@Override
protected Integer compute() {
if(toIndex-fromIndex < THRESHOLD) {
int total= 0;
for(int i=fromIndex;i<=toIndex;i++) {
//SleepTools.ms(1);
total= total+ src[i];
}
return total;
}else {
//fromIndex....mid....toIndex
int mid = (fromIndex+toIndex)/2;
SumTask left = new SumTask(src,fromIndex,mid);
SumTask right = new SumTask(src,mid+1,toIndex);
invokeAll(left,right);
return left.join()+right.join();
}
}
}
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
//获取我们创建的数组
int[] src = TestArray.testArray();
SumTask innerFind = new SumTask(src,0,src.length-1);
long start = System.currentTimeMillis();
pool.invoke(innerFind);//同步调用
System.out.println("Task is Running.....");
System.out.println("The total is "+innerFind.join()
+" spend time:"+(System.currentTimeMillis()-start)+"ms");
}
}
同样我们第一次输出,将睡眠线程注释,输出:(注:可以尽量把数组调大,来看差距),性能反而比不用ForkJoin要慢
而不注释线程睡眠代码:,性能提高了大约三倍
结论:我们知道线程上下文切换是需要时间的,所以当一些业务非常简单的时候,完全不需要cpu做什么复杂操作,这时候我们直接让主线程去操作即可,并不是所有业务操作,都用多线程就一定能提高效率
一、Fork/Join的异步用法同时演示不返回结果值:
注:这里我们继承的类为RecursiveAction:它是没有返回值的,这里我们采用异步的方式去调用
/**
*类说明:遍历指定目录(含子目录)找寻指定类型文件
*/
public class FindDirsFiles extends RecursiveAction{
private File path;//当前任务需要搜寻的目录
public FindDirsFiles(File path) {
this.path = path;
}
public static void main(String [] args){
try {
// 用一个 ForkJoinPool 实例调度总任务
ForkJoinPool pool = new ForkJoinPool();
FindDirsFiles task = new FindDirsFiles(new File("F:/"));
pool.execute(task);//异步调用
System.out.println("Task is Running......");
//为了让我们更加直观的看出这是异步操作,我们先让主线程休眠100毫秒
Thread.sleep(100);
int otherWork = 0;
for(int i=0;i<=100;i++){
otherWork = otherWork+i;
}
System.out.println("Main Thread done sth......,otherWork="+otherWork);
task.join();//阻塞的方法
System.out.println("Task end");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void compute() {
List<FindDirsFiles> subTasks = new ArrayList<>();
File[] files = path.listFiles();
if(files!=null) {
for(File file:files) {
if(file.isDirectory()) {
subTasks.add(new FindDirsFiles(file));
}else {
//遇到文件,检查
if(file.getAbsolutePath().endsWith("txt")) {
System.out.println("文件:"+file.getAbsolutePath());
}
}
}
if(!subTasks.isEmpty()) {
for(FindDirsFiles subTask:invokeAll(subTasks)) {
subTask.join();//等待子任务执行完成
}
}
}
}
}
输出:
第一句
,在这里异步线程刚刚分配完资源,可能正处于就绪状态,而我们主线程是处于运行状态,所以我们先行打印了第一句话
第二句:
在文件中间,我们找到了第二句,由此可以看出这俩个线程是并行的
第三局:
我们这里使用了join方法,所以主线程会在这里等待我们的异步线程执行完后再执行接下来的工作