Fork/Join 框架主要由Fork 和 Join 两个操作构成:
1)Fork操作主要用于对任务和数据进行划分,一般是将一个大问题划分为几个小问题,以期望能够方便地对简化后地问题进行处理
2)Join操作主要作用于对各个部分地运行结果进行合并,相当于程序中的一个障栅。
这两个操作与MapReduce中的Map/Reduce操作类似,简而言之,Fork/Join框架是一种设计模式,是一种思想,在其他编程语言中也同样适用。
Fork/Join 框架实现了任务的定义和任务处理功能的分离,使程序员在现实并行编程的同时,可以更好的集中精力实现业务逻辑相关的内容,这大大降低了并行程序设计的难度。
在Java中,Fork/Join 框架实现了ExecutorService接口,与任何以一个ExecutorService接口的功能相同的是:Fork/Join会自动地将任务分配给线程池中地线程,并负责线程管理地相关工作。
与ExecutorService不同地是:Fork/Join使用了工作窃取算法,已经完成自身任务线程可以从其他繁忙地线程中窃取任务来执行,从而保持了线程执行过程中地负载均衡。
Fork/Join 主要用途:Fork/Join框架通常被用于解决那些可以递归地分解为更小地任务地问题。该框架与负载均衡、分治(Divide and Conquer)方法及工作窃取算法(Work-steaing Algorithm)有密切地联系。
Fork/Join地负债均衡
负载不均衡会导致一些线程空闲,从而造成资源浪费。常见了负载不均衡地原因是分配给某个线程地任务过多,其他线程都执行完毕,但该线程仍继续执行。
负载均衡是指在CPU处理器核上运行地线程全部以相同地繁忙程度来工作,理想情况下,所有地线程都都成同样大小的工作量。
负载均衡有利于加快程序的执行,减少资源浪费,因此我们在设计并行程序时,也应该保证负载均衡,但有些情况的负债均衡任务是由操作系统或JVM虚拟机来完成的。
Frok/Join的分治方法
分治方法是简化问题的一种处理方法,该方法的基本思想是将一个复杂的任务分解为若干个小任务,然后分别解决这些小任务。
使用分治方法的步骤:
1)分解。将复杂的任务分成小任务,直到把任务分解到容易解决为止。
2)解决。对已完成分解的小人物进行求解。
3)j结果合并。如果每个任务都有返回结果,则需要对结果进行合并。
分治法可以解决合并排序,二分搜索和矩阵相乘问题。
在使用Fork/Join框架的过程中,首先对ForkJoinTask进行分解,任务分解的数目可以根据问题的特征以及CPU可同时处理的线程数进行设定,然后将分解的任务交给ForkJoinPool处理,最后对处理结果进行收集。
工作窃取算法:
工作窃取算法是提高程序性能,保证负债均衡的一种算法。该算法的基本思想是当程序中某些线程做完自身的工作后,去查看其他线程是否还有未处理完成的工作,如果有,则窃取一部分工作来执行,从而帮助那些未完成的
程序尽快完成他们的工作。
程序在使用该算法后,一方面,可以保证线程始终处于一种忙碌状态,提高资源的利用率;另一方面,也可以减少其他繁忙线程的处理时间,有助于提高程序的性能。
从本质上来说,工作窃取算法是一种任务调度方法,尽量使每个线程都能处于忙碌状态。生活中"窃取"是一个编译词,但在这里却有很积极的意义。
Fork/Join框架与Executor框架的不同之处在于Fork/Join框架采用了工作窃取算法,可以说该算法是Fork/Join框架的核心。在ForKJoinPool中,有一些线程执行任务较快,在做完自己的工作之后,这些线程将尝试发现那些未被执行的任务,
如果找到,则执行这些任务。
Fork/Join框架采用双端队列(Deque)作为任务的存储结构,该队列支持后进先出的数据pop和push操作,并且支持先进先出的take操作。由某一工作线程创建的子任务仍然会被加入到线程的队列中,一般采用push操作,工作线程从自己的
双端队列中取出执行任务,一般采用pop操作,当工作线程需要从其他线程的工作队列中窃取任务时,一般采用take操作。
线程池中的线程每次是从队列的头部取出任务来执行,当使用使用fork操作产生新任务时,会把新的任务加入到队列的头部,而不像其他线程池加入到尾部,这样可以保证fork出来的新的任务可以尽快得到执行。
当某个线程执行完自己的任务,而没有其他任务可处理时,就从队列尾部窃取一个任务执行。
Fork/Join 框架的编程技术
java中Fok/Join框架比较适于解决那些具有递归性质,可以进行任务分解的程序。
Fork/Join框架需要对任务进行分解和合并操作,在分解前,首先查看问题的规模是否超过了预设门槛值(Threshold Value),在任务规模不大的情况下,采用串行的解决方式,由于该方式省去了分解和合并的操作有时效果会更好。
在使用Fork/Join框架时,门槛值通常时人为地进行设定,当问题地规模小于门槛值时,说明没必要采用并行地解决方式,因此更倾向于采用串行执行方式或者采用其他更优化地算法解决;
而当问题地规模大于门槛值时,采用Fork/Join框架求解。
使用Fork/Join框架编程模式:如下所示:
if(问题模式<门槛值){
//使用串行模式解决或者其他模式解决
}else{
//将任务Task尽心分解,分解为若干个小任务 Task1,Task2.......
//将任务Task1、Task2提交到线程池执行
//如果任务有返沪结果,收集结果
}
ForkJoinPool 类
ForkJoinPool类是Fork/Join框架地核心,也是Fork/Join框架执行地入口点,它是实现了接口Executor Service。ForkJoinPool类地任务是负责管理线程,并提供线程执行状态和任务处理地相关信息。
ForkJoinPool类区别于其他ExecutorService的地方在于该类实现了工作窃取算法,在线程池中的线程总是尝试发现其他可以运行的任务,可以通过相关的方法获取工作窃取的执行情况。
ForkJoinPool的创建
ForkJoinPool类从AbstractExecutorService类继承,主要用于处理ForkJoinTask中的任务。
以下是ForkJoinPool类构造方法的形式:
1)ForkJoinPool(); //该构造方法将默认生成一个线程池,线程池中可以同时运行的线程数和CPU能够同时运行的最大线程数(可以通过Runtime.getRuntim.availableProcessors()获取)相同。
2)ForkJoinPool(int parallelism);//用户可以指定线程池中线程的数目。
//parallelism 指明并行运行的线程数
//factory 是线程工厂,用于创建新线程
//handler 用户处理内部出现的异常
//asyncMode 用于指定ForkJoinPool的工作模式,当为true时,表示工作本地的先进先出模式。 默认为false。对于某些应用来讲,为true比默认的更加合适。
3)ForJoinPool(int parallelism,ForkJoinWorkThreadFactory factory,Thread.UncaughtExceptionHandler handler,boolean asyncMode);//
demo 示例:
创建一个ForkJoinPool
ForkJoinPool pool = new ForkJoinPool();
ForkJoinPool pool = new ForkJoinPool(8);
ForkJoinPool的使用:
ForkJoinPool的使用大致可以分为两种,一种时通过invoke、execute和submit执行的任务,另一种时在程序执行过程中通过fork操作来执行的任务。
ForkJoinPool常用的方法:
1)invoke(ForkJoinTask<T> task);//是一个同步调用方法,用于处理给定任务,并在处理完毕后返回处理的结果,返回结果的类型由T指定。
2)invokeAll(Collection<? extends Callable<T> >tasks);//是一个同步调用方法,可以将若干个任务组成的一个集合交给ForkJoinPool执行,任务在继续运行之前会等待子任务的结束,该方法
返回任务的结果,结果类型由T指定。ForkJoinTask类的该方法是Executor和Fork/Join框架的主要不同之处。
3)execute(Runnable task);//可以把一个Runnable线程所有代表的任务收纳柜ForkJoinPool中,需要指出的是:ForkJoinPool不会对Runnable对象使用工作窃取算法,该算法只会被应用到ForkJoinTask对象中。
同样,ForkJoinPool不会对Callable对象使用工作窃取算法。
4)submit(ForkJoinTask<T> task);//用于把一个任务提交给ForkJoinPool执行,返回ForkJoinTask<T>的结果。
以上三类执行任务的方法,具体使用如下:
------------------------------------------------------------------------------------------------------------------------
| 在外部对Fork/Join操作的调用 | 在Fork/Join框架范围内使用
------------------------------------------------------------------------------------------------------------------------
异步执行 | execute(ForkJoinTask) | ForkJoinTask.fork()
------------------------------------------------------------------------------------------------------------------------
同步执行(等待子任务完成) | invoke(ForkJoinTask) | ForkJoinTask.invoke()
------------------------------------------------------------------------------------------------------------------------
执行并获得结果 | submit(ForkJoinTask) | ForkJoinTask.fork()
------------------------------------------------------------------------------------------------------------------------
Fork/Join框架中的任务
ForkJoinTask类是所有的在Fork/Join框架中执行任务的基类,提供了一系列机制来实现Fork和Join操作,该类由两个子类,分别是RecursiveAction和RecursiveTask,从RecursiveAction类继承类的子类方法一般没有返回值,
从RecursiveTask类继承的子类则由返回值。
public abstract class ForkJoinTask<?> extends Object implements Future<V>,Serializable
ForkJoinTask 类实现了接口Serializable 和 Future<V>,所以一般在子类加入serialVersionUID变量定义。 如:
private static final long SerialVersionUID = 1L;
Fork/Join 任务的创建
在创建任务时,最好不要从ForkJoinTask类直接继承,而是从该类的子类RecursiveAction或RecursiveTask继承。
1)从RecursiveAction 继承承建任务
从RecursiveAction类继承的子类方法一般没有返回值。继承后的新类需要重写该类的computer()方法。
computer()方法的形式如下:
@Override
public void computer(){
//方法体
}
isDone()方法,用于判断任务是否完成。
cancel(booleanmayInterrupteIfRunning)用于取消一个任务的执行。
当任务提交到ForkJoinPool后获得运行的机会。
demo 示例:
使用Fork/Join 框架对班级的人数进行更新。当需要更新班级的人超过10时,更新任务进行细分。
分析:Fork/Join 框架的门槛 10
//班级的对象类
public class ClassInfo{
private String name;
private int number;
public ClassInfo( String name,int number){
this.name = name;
this.number = number;
}
public String getName(){
return name;
}
public int getNumber(){
return number;
}
public void setNumber(int number){
this.number = number;
}
}
/更新操作的任务类
public class UpdateTask extends RecursiveAction{
private static final long serialVersionUID =1L;
private List<ClassInfo> classInfos;
private int start;
private int end;
private int increment;
private int nthreads;
private int threshold;
public UpdateTask(List<ClassInfo> classInfos,int start,int end,int increment,int nthreads,int threshold){
this.classInfos = classInfos;
this.start = start;
this.end = end;
this.increment = increment;
this.nthreads = nthreads;
this.threshold = threshold;
}
@Override
public void compute(){
//门槛 串行
if(end-start<=threshold | threshold ==1){
updateSequential();
}else{
UpdateTask [] tasks = new UpdateTask[nthreads];
int [] data = new int[nthreads+1];
int segment = (end -start+nthreads-1)/nthreads;
for(int i=0;i<=nthreads;i++){
data[i] = start+segment*i;
if(data[i]>end){
data[i] = end;
}
}
int mid = (end=start)/2;
for(int i=0;i<nthreads;i++){
tasks[i] = new UpdateTask(classInfos,data[i],data[i+1],increment,nthreads,threshold);
}
invokeAll(tasks);
}
}
public void updateSequential(){
for(int i = start;i<end;i++){
ClassInfo classInfo = classInfos.get(i);
classInfo.setNumber(classInfo.getNumber()+increment);
}
}
}
//启动测试类
public class Index{
public static void main(String [] args ){
int nthreads = Runtime.getRuntime().availableProcessors();
int threshold = 10;
int increment = 5;
int baseNum = 50;
int size = 10000;
List<ClassInfo> classInfos = new ArrayList<ClassInfo>();
for(int i=0;i<size;i++){
ClassInfo classInfo = new ClassInfo("班级"+i,baseNum);
classInfos.add(classInfo);
}
ForkJoinPool pool = new ForkJoinPool();
UpdateTask updateTask = new UpdateTask(classInfos,0,classInfos.size(),increment,nthreads,threshold);
pool.execute(updateTask);
do{
System.out.printf("类Index:并行度:%d\n",pool.getParallelism());
System.out.printf("类Index:活动线程数:%d\n",pool.getActiveThreadCount());
System.out.printf("类Index:任务数:%d\n",pool.getQueuedTaskCount());
System.out.printf("类Index:%d\n",pool.getStealCount());
try{
TimeUnit.SECONDS.sleep(1);
}catch(InterruptedException e){
e.printStackTrace();
}
}while(!updateTask.isDone());
if(validate(classInfos,baseNum+increment)){
System.out.println("所有班级更新完毕!");
}else{
System.out.println("Something wrong happend.");
}
}
public static boolean validate(List<ClassInfo> classInfos, int total){
boolean pass = true;
for(ClassInfo info:classInfos){
if(info.getNumber()!=total){
pass = false;
}
}
return pass;
}
}
运行结果:
类Index:并行度:8
类Index:活动线程数:0
类Index:任务数:0
类Index:41
所有班级更新完毕!