一. 并行流与串行流
并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块中的流。JDK8中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream Api可以声明式的通过parallel()与sequential()在并行流和串行流(又被称作“顺序流”)之间进行切换。
二. Fork/Join框架
Fork/Join框架就是在必要的情况下,将一个大任务拆分(fork)成若干个子任务(拆到不可再拆为止),再将一个个子任务就是并行运算,最终将值进行join汇总。
三. Fork/Join框架与传统线程池的区别
传统的线程池虽然也能把任务进行拆分成若干子任务,借助不同线程来执行任务,但有一个问题:一旦某个子任务由于某些原因无法继续执行,那么负责执行该任务的线程将进入阻塞状态,影响了分配给该线程的其他后续任务的执行。
在Fork/Join框架中情况则大不相同。如果线程A被某个子任务阻塞,A不会进入阻塞状态,而是会主动寻找尚未被执行的其它子任务(可以从自己的后续任务队列中找,也可以从其它线程的任务队列中偷)。这种模式被称作“工作窃取”模式(Working Stealing),当执行新的任务时,线程利用Fork/join框架可以将任务拆分成更小的任务,并将小任务分配/加入到线程队列中,如果自己空闲,就会从一个随机线程的任务队列中偷一个尚未被执行的任务来运行。
其实早在jdk1.7中已经有Fork/Join的实现了,下面来看看在JDK1.7中如何使用:
/**
* jdk1.7中使用Fork/Join框架
*
* 需要定义:
* 1. 如何拆分
* 2. 拆到什么程度为止
*/
public class ForkJoinCalculate extends RecursiveTask<Long> {
private static final long serialVersionUID = 1212853982210556381L;
private long start;
private long end;
//拆分的阈值,任务的最小单位是100000,拆到这个程度就不用再拆了
private static final long THRESHOLD = 100000;
public ForkJoinCalculate(long start, long end){
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long length = start - end;
if(length <= THRESHOLD){ //进行求和
long sum = 0;
for(long i = start; i <= end; i++){
sum += i;
}
return sum;
}else{ //进行拆分
long middle = (start + end) / 2;
ForkJoinCalculate left = new ForkJoinCalculate(start, middle);
left.fork(); //拆分子任务,并将任务压入线程队列
ForkJoinCalculate right = new ForkJoinCalculate(middle + 1, end);
right.fork();
//结果汇总
return left.join() + right.join();
}
}
}
/**
* 使用Fork/join求和
*/
@org.junit.Test
public void test2(){
Instant start = Instant.now();
//需要ForkJoin线程池的支持
ForkJoinPool pool = new ForkJoinPool();
ForkJoinCalculate fork = new ForkJoinCalculate(0, 1000000000L);
Long sum = pool.invoke(fork);
System.out.println(sum);
Instant end = Instant.now();
System.out.println(Duration.between(start, end).toMillis());
}
jdk1.8的写法如下:
/**
* 使用jdk1.8求和
*/
public void test3(){
Instant start = Instant.now();
Long sum = LongStream.rangeClosed(0, 1000000000L)
.parallel()
.reduce(0, Long::sum);
Instant end = Instant.now();
System.out.println(Duration.between(start, end).toMillis());
}
注意,由于Fork/Join会充分发挥CPU多核性能,因此电脑不好的朋友不要轻易运行上述代码,或者在测试时把计算的数据量调低。我的笔记本CPU型号i5-6300HQ ,直接死机了。