分而治之策略
当我们要处理很大的数据,一个重要的思想就是把问题划分成若干个小问题处理,然后把小问题的结果进行整合,得到最终的结果。在JDK中有一个ForkJoin线程池,使用fork/join方法处理数据。Fork/Join 模式有自己的适用范围。如果一个应用能被分解成多个子任务,并且组合多个子任务的结果就能够获得最终的答案,那么这个应用就适合用 Fork/Join 模式来解决
ForkJoinPool线程池
java.util.concurrent.ForkJoinPool 继承了 AbstractExecutorService类,这个线程池提供了下面几种执行任务的方法,有些有返回值有些没有返回值。有些要求任务是Runnable对象,有些要求任务是Callable对象
它的无参数构造方法,返回跟系统CPU数量一样的线程
public ForkJoinPool() {
this(Math.min(MAX_CAP, Runtime.getRuntime().availableProcessors()),
defaultForkJoinWorkerThreadFactory, null, false);
}
其中一个重要的方法是,提交ForkJoinTask对象的任务,返回ForkJoinTask对象的结果
public <T> ForkJoinTask<T> submit(ForkJoinTask<T> task) {
if (task == null)
throw new NullPointerException();
externalPush(task);
return task;
}
ForkJoinTask任务
ForkJoinTask任务就是支持fork()分解和join()等待的任务。
ForkJoinTask是一个J.U.C下的一个抽象类
public abstract class ForkJoinTask<V> implements Future<V>, Serializable
这个类中有两个方法
fork()方法,当ForkJoinTask任务执行fork()方法,会把ForkJoinTask任务提交给ForkJoinPool
join方法,ForkJoinTask任务执行join()方法,会把结果返回。
它有两个实现类
① public abstract class RecursiveTask<V> extends ForkJoinTask<V> 带有返回结果的任务
② public abstract class RecursiveAction extends ForkJoinTask<Void> 没有返回结果的任务
通常我们并不直接继承 ForkJoinTask,它包含了太多的抽象方法。针对特定的问题,我们可以选择 ForkJoinTask 的不同子类来完成任务。RecursiveAction 是 ForkJoinTask 的一个子类,它代表了一类最简单的 ForkJoinTask:不需要返回值,当子任务都执行完毕之后,不需要进行中间结果的组合。如果我们从 RecursiveAction 开始继承,那么我们只需要重载 protected void compute()
方法
RecursiveTask 是 ForkJoinTask 的一个子类,它是需要返回值的任务,当子任务都执行完毕之后,进行中间结果的组合。如果我们从RecursiveTask 开始继承,那么我们只需要重载 protected void compute()
方法,它可使用泛型指定一个返回值的类型
下面这个例子解决这样一个问题就用到带有返回结果的任务。
如何充分利用多核CPU,计算很大List中所有整数的和?
那一个大的任务分成几个小任务来执行。而且在ForkJoinPool线程池中,每一个线程对应一个任务队列,如果线程A已经完成了自己的任务,发现B线程的任务队列还有很多,它会去B的任务队列从后面拿任务来执行,如果任务时ForkJoinTask类型的,就支持join(),一起返回。
package xidian.lili.reentrantlock;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.RecursiveTask;
public class CountTask extends RecursiveTask<Long>{
public int start;
public int end;
public final int THROLDSOLD=1000;
public CountTask(int start, int end) {
this.start = start;
this.end = end;
}
protected Long compute() {
Long sum=0L;
if((end-start)<THROLDSOLD){
for(int i=start;i<=end;i++){
sum+=i;
}
}else{
int step=(start+end)/100;
ArrayList<CountTask> subtasks=new ArrayList();
int pos=start;
for(int i=0;i<100;i++){
int lastone=pos+step;
if(lastone>end)lastone=end;
CountTask subtask=new CountTask(pos,lastone);
subtasks.add(subtask);
pos=lastone+1;
subtask.fork();
}
for(CountTask subtask:subtasks){
sum+=subtask.join();
}
}
return sum;
}
public static void main(String[] args) {
CountTask task=new CountTask(0,10000);
ForkJoinPool pool=new ForkJoinPool();
ForkJoinTask<Long> result=pool.submit(task);
try {
long res=result.get();
System.out.println(res);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
问题
使用Fork/Join模式,如果任务的层次划分的很深,一直得不到返回值,一可能就是任务太多,系统内的线程数多,导致系统性能下降。二可能调用层次太深导致栈溢出
当我们上述任务的子任务划分更小,也就是调用层次更深,出现了OOM异常