ForkJoin
ForkJoin是由JDK1.7后提供多线并发处理框架。ForkJoin的框架的基本思想是分而治之。什么是分而治之思想呢?通俗来讲就是将一个复杂的任务分成(Fork)若干个子任务(拆分到不能再拆分,也就是达到我们制定的拆分临界值,然后将每个子任务的结果合并(Join)起来。
ForkJoin使用
ForkJoin的思想就是将一个拆分成多个子任务,那么这些子任务就要存到一个池子里,也就是ForkJoinPool。
ForkJoinPool与普通线程池的区别
ForkJoin的原理就是工作窃取,什么是工作窃取了,通俗来讲就是两个人干活,一个干的快,一个干的慢,干的快的人干完活之后并没有停下来,而是帮干的慢的人,这就是工作窃取。但是也并不是随便的从某一个地方开始,任务被拆分成多个放到队列中,执行快的线程执行完之后会从干的慢的线程队列末尾中偷取过来帮它执行。由此也带来一个弊端,那就就是会出现资源抢夺的问题:
当线程B执行完后从A末尾中开始执行,等到一定的时候两个就会A,B就会同时要用一个资源,这样就会产生抢夺的问题。
ForkJoin框架实现
使用ForkJoin我们就要创建一个ForkJoinTask,而ForkJoinTask是一个抽象类,但是我们并不用自己去实现它,ForkJoinTask给我们提供了两个子类RecursiveAction、RecursiveTask。一个是递归事件没有返回值,一个是递归任务有返回值。
我们接下来计算一下1到10,0000,000之间的和
任务类
public class ForkJoinWork extends RecursiveTask<Long> {
private Long start;
private Long end;
private static final Long tempLong=10000L;//临界值:只要超过了这个值ForkJoin效率就会更好
public ForkJoinWork(Long start, Long end) {
this.start = start;
this.end = end;
}
/**
* 计算方法
* @return 计算结果
*/
@Override
protected Long compute() {
//临界值判断
if ((end-start)<tempLong){
Long sum=0L;
for (Long i=start;i<end;i++){
sum+=i;
}
return sum;
}else {
//第二种方式
long middle=(end+start)/2;
ForkJoinWork right=new ForkJoinWork(start,middle);
right.fork();//压入线程队列
ForkJoinWork left=new ForkJoinWork(middle+1,end);
left.fork();//压入线程队列
//获得结果 join 会阻塞等待结果
return right.join()+left.join();
}
}
}
测试类:
public class MyTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
test1();
test2();
test3();
}
// 正常测试
public static void test1(){
long start = System.currentTimeMillis();
Long sum = 0L;
for (Long i = 0L; i <= 10_0000__0000 ; i++) {
sum +=i;
}
long end = System.currentTimeMillis();
System.out.println("time:"+(end-start)+" sum:"+sum);
}
// ForkJoin测试
public static void test2(){
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinWork forkJoinDemo = new ForkJoinWork(0L,10_0000__0000L);
Long sum = forkJoinPool.invoke(forkJoinDemo);
long end = System.currentTimeMillis();
System.out.println("time:"+(end-start)+" sum:"+sum);
}
// Stream并行流测试
public static void test3(){
long start = System.currentTimeMillis();
long sum = LongStream.rangeClosed(0, 10_0000__0000).parallel().reduce(0, Long::sum);
long end = System.currentTimeMillis();
System.out.println("time:"+(end-start)+" sum:"+sum);
}
}
在这里为了做比较提供了三种计算方式,一个传统方式,一个使用ForkJoin,另外一个使用并行流。计算结果如下:
通过图片我们可以看到在执行大数据的时候并行流效率>ForkJoin>传统方式
但是计算量小的时候,情况就不一样了!
当我们计算1到2的和的时候,我们直接看下运行效果:
从运行结果中我们可以看到传统方式的效率>ForkJoin>并行流。
所以在大数据场景下我们用ForkJoin和并行流效果会好些,而在数据很小的时候我们并不需要用ForkJoin ,用传统方式即可,我们一定要在具体的场景去使用,而不是不管什么场景都使用ForkJoin。
异步回调(Future)
Future和前端的Ajax其实是一样的,都是为了给我们提供一个异步回调的通知,提高程序的运行的效率!Future设计的初衷是对将来会发生的结果进行建模。
下面我们看一下Future的类结构图,发现其实ForkJoin实现了Future接口。
异步回调也分两种情况,一种是有结果的,一种是没有结果的,我们来看一段代码就知道:
public class CompletableFutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//没有返回值
// CompletableFuture<Void> completableFuture=CompletableFuture.runAsync(()->{
// try {
// TimeUnit.SECONDS.sleep(2);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName()+"没有返回值!");
// });
// System.out.println("11111");
// completableFuture.get();
//有返回值
CompletableFuture<Integer> completableFuture=CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"=>supplyAsync!");
int i=10/0;
return 1024;
});
System.out.println(completableFuture.whenComplete((t,u)->{
System.out.println("t=>"+t);// 正常结果
System.out.println("u=>"+u);// 信息错误!
}).exceptionally(e->{
System.out.println(e.getMessage());
return 500;// 异常返回结果
}).get());
}
}