概述
Fork/Join
框架是jdk1.7
提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。其思想和MapReduce
的思想非常类似。对于任务的分割,要求各个子任务之间相互独立,能够并行独立地执行任务,互相之间不影响。
ForkJoinPool任务调度器
ForkJoinPool
是ForkJoin
框架中的任务调度器,和ThreadPoolExecutor
一样实现了自己的线程池,提供了三种调度子任务的方法:
execute
:异步执行指定任务,无返回结果;invoke、invokeAll
:异步执行指定任务,等待完成才返回结果;submit
:异步执行指定任务,并立即返回一个Future
对象;
fork-join
框架中的实际的执行任务类,有以下两种实现,一般继承这两种实现类即可。
RecursiveAction
:用于无结果返回的子任务;RecursiveTask
:用于有结果返回的子任务;
有返回结果的任务
package com.example.lock;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
import com.alibaba.fastjson.JSONObject;
public class ForkJoinTask2 extends RecursiveTask<List<JSONObject>>{
private static final long serialVersionUID = 1L;
//任务切割阈值
private static final int THRESHOLD =5;
private int start;
private int end;
private List<JSONObject> list;
public ForkJoinTask2(int start,int end,List<JSONObject> list){
this.start=start;
this.end=end;
this.list=list;
}
@Override
protected List<JSONObject> compute() {
System.out.println("进入并行任务处理方法...........................");
List<JSONObject> lists=new ArrayList<JSONObject>();
//首先需要判断任务是否小于等于阈值1000 ,如果是就直接执行任务。
if(end-start<=THRESHOLD){
for (int i = start; i <=end; i++){
lists.add(list.get(i));
}
System.out.println(Thread.currentThread().getName()+"处理 "+start+"到"+end+"页的数据处理任务");
return lists;
}else{
/**
* 否则分割成两个子任务
* 每个子任务在调用fork方法时,又会进入compute方法,看看当前子任务是否需要继续分割成子任务,
* 如果不需要继续分割,则执行当前子任务并返回结果。使用join方法会阻塞并等待子任务执行完并得到其结果。
*/
int mid=(start+end)/2;
ForkJoinTask2 task1=new ForkJoinTask2(start, mid,list);
task1.fork();
ForkJoinTask2 task2=new ForkJoinTask2(mid+1, end,list);
task2.fork();
lists.addAll(task1.join());
lists.addAll(task2.join());
return lists;
}
}
public static void main(String[] args) {
long start=System.currentTimeMillis();
ForkJoinPool poll=new ForkJoinPool();
List<JSONObject> listCount=new ArrayList<>();
for (int i = 0; i <=20; i++) {
JSONObject obj= new JSONObject();
obj.put("id", i);
listCount.add(obj);
}
System.out.println("数据总大小"+listCount.size());
List<JSONObject> sumList=poll.invoke(new ForkJoinTask2(0, 19,listCount));
System.out.println("并行处理返回结果:"+sumList.size());
sumList.forEach(System.out::println);
System.out.println(System.currentTimeMillis()-start+"ms");
}
}
无返回值的任务
public class RaskDemo extends RecursiveAction {
/**
* 每个"小任务"最多只打印20个数
*/
private static final int MAX = 5;
private int start;
private int end;
private List<String> list;
public RaskDemo(int start, int end,List<String> list) {
this.start = start;
this.end = end;
this.list=list;
}
@Override
protected void compute() {
//当end-start的值小于MAX时,开始打印
if((end-start) < MAX) {
for(int i= start; i<end;i++) {
System.out.println(Thread.currentThread().getName()+"list.get(i);的值"+list.get(i));
}
}else {
System.out.println("任务拆分执行");
// 将大任务分解成两个小任务
int middle = (start + end) / 2;
RaskDemo left = new RaskDemo(start, middle,list);
RaskDemo right = new RaskDemo(middle, end,list);
left.fork();
right.fork();
}
}
public static void main(String[] args) throws InterruptedException {
// 创建包含Runtime.getRuntime().availableProcessors()返回值作为个数的并行线程的ForkJoinPool
List<String> lists=Arrays.asList("1","2","3","4","5","6","7","8","9","10","11","12","13","14","15");
ForkJoinPool forkJoinPool = new ForkJoinPool();
// 提交可分解的PrintTask任务
forkJoinPool.submit(new RaskDemo(0, lists.size(),lists));
//阻塞当前线程直到 ForkJoinPool 中所有的任务都执行结束
// forkJoinPool.awaitTermination(2, TimeUnit.SECONDS);
// 关闭线程池
// forkJoinPool.shutdown();
forkJoinPool.awaitTermination(30, TimeUnit.SECONDS);
System.out.println("dadasdsa");
}
}
在
jdk1.8
中的并行流就是for join
的实现原理
fork-Join 与传统线程池的区别
- 采用 “
工作窃取”模式(work-stealing)
:当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。 - 相对于一般的线程池实现,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态,
- 而在
fork/join
框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行.那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行.这种方式减少了线程的等待时间,提高了性能。
工作窃取算法
- 工作窃取算法是指某个线程从其它任务里窃取任务来执行,为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务的线程永远从双端队列头部拿任务执行,而窃取任务的线程永远从双端队列尾部拿任务执行。
- 优点:充分利用线程进行并行计算,减少线程简单竞争
- 缺点:在某些情况下还是存在竞争。比如双端队列里只有一个任务时。并且该算法会消耗了更多的系统资源,比如创建了多个线程和多个双端队列。