简介
如果计算大单位的数值,可以使用ForkJoinPool来实现,它的原理是将一个大任务,通过递归拆分成很多小任务,每一个小任务就是一个线程来执行。
实现步骤:
- 继承
RecursiveTask
(有返回值)或者RecursiveAction
类 - 重写 compute 方法,并在里面写自己的 任务分支 任务合并逻辑
- 调用方式:ForkJoinPool类中的invoke方法
ForkJoin的使用
通过ForkJoin计算0~1亿的总合:
public class ThreadForkJoinPool {
public static void main(String[] args) {
long end = 1 * 10000 * 10000;//计算一亿
//ForkJoin框架计算
Instant startTime = Instant.now();//java8新特性
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinSumCalculate(0L,end);
Long sum = pool.invoke(task);
System.out.println("ForkJoin框架计算 得出结果:"+sum);
}
}
class ForkJoinSumCalculate extends RecursiveTask<Long>{
private long start;
private long end;
private static final long THURSHOLD = 10000L;//临界值,根据CPU设定
public ForkJoinSumCalculate(long start, long end) {
super();
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long length = end - start;
if (length <= THURSHOLD) {//达到临界值运算结果
long sum = 0L;
for (long i = start;i <= end;i++) {
sum += i;
}
return sum;
} else {//未达到临界值 继续拆分
long middle = (start + end)/2;
ForkJoinSumCalculate left =new ForkJoinSumCalculate(start,middle);
left.fork();//进行拆分,同时压入线程队列
ForkJoinSumCalculate right =new ForkJoinSumCalculate(middle+1,end);
right.fork();
return left.join() + right.join();//对结果合并
}
}
}
打印结果:
ForkJoin框架计算 得出结果:5000000050000000
ForkJoin性能测试
ForkJoin 对比普通单线程 和java8 运算时间。
long end = 1 * 10000 * 10000;//计算一亿
//ForkJoin框架计算
Instant startTime = Instant.now();//java8新特性
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinSumCalculate(0L,end);
Long sum = pool.invoke(task);
Instant endTime = Instant.now();
System.out.println("ForkJoin框架计算 得出结果:"+sum+",计算时间:"+Duration.between(startTime, endTime).toMillis());//转毫秒
//单线程for计算
startTime = Instant.now();
long forSum = 0;
for (long i = 0;i<= end;i++) {
forSum += i;
}
endTime = Instant.now();
System.out.println("单线程for计算 得出结果:"+forSum+",计算时间:"+Duration.between(startTime, endTime).toMillis());//转毫秒
//java8 封装ForkJoin计算
startTime = Instant.now();
long java8Sum = LongStream.rangeClosed(0L,end)
.parallel()//并行流
.reduce(0L,Long::sum);
endTime = Instant.now();
System.out.println("Java8 封装ForkJoin计算 得出结果:"+java8Sum+",计算时间:"+Duration.between(startTime, endTime).toMillis());//转毫秒
打印结果(计算结果不是绝对的,会随着机器不同而影响,java8性能最差?):
为了更细致的看结果:我们对这3种方式 通过如下比较:分别拆成3个方法,数据大小比较,cpu占用率比较:(这里不用JMX、Jstack工具了)
先看一下当前cpu占有率:
使用自己封装的forkJoin计算10亿:
long end = 1 * 10000 * 10000 * 10;//计算十亿
//ForkJoin框架计算
Instant startTime = Instant.now();//java8新特性
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinSumCalculate(0L,end);
Long sum = pool.invoke(task);
Instant endTime = Instant.now();
System.out.println("ForkJoin框架计算 得出结果:"+sum+",计算时间:"+Duration.between(startTime, endTime).toMillis());//转毫秒
CPU占用率最高54%
打印输出:ForkJoin框架计算 得出结果:500000000500000000,计算时间:442
单线程性能测试
使用单线程的for循环计算10亿
long end = 1 * 10000 * 10000 * 10;//计算十亿
Instant startTime = Instant.now();
long forSum = 0;
for (long i = 0;i<= end;i++) {
forSum += i;
}
Instant endTime = Instant.now();
System.out.println("单线程for计算 得出结果:"+forSum+",计算时间:"+Duration.between(startTime, endTime).toMillis());//转毫秒
CPU上升10个百分点。
打印输出:单线程for计算 得出结果:500000000500000000,计算时间:3099
java8性能测试
long end = 1 * 10000 * 10000 * 10;//计算十亿
Instant startTime = Instant.now();
long java8Sum = LongStream.rangeClosed(0L,end)
.parallel()//并行流
.reduce(0L,Long::sum);
Instant endTime = Instant.now();
System.out.println("Java8 封装ForkJoin计算 得出结果:"+java8Sum+",计算时间:"+Duration.between(startTime, endTime).toMillis());//转毫秒
cpu占用率:91%
打印输出:Java8 封装ForkJoin计算 得出结果:500000000500000000,计算时间:2266
计算0~100万总和:
ForkJoin框架计算 得出结果:500000500000,计算时间:7
单线程for计算 得出结果:500000500000,计算时间:5
Java8 封装ForkJoin计算 得出结果:500000500000,计算时间:69
数据量越少单线程的优势就体现出来了