Fork/Join框架是Java 7提供的一个用于并行执行任务的框架, 核心思想就是把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果。
如果对算法有所了解的话,很清楚就能看出来Fork/join框架与分治算法很相似。比如计算1+2+…+10000,可以分割成10个子任务,每个子任务分别对1000个数进行求和,最终汇总这10个子任务的结果。
执行流程如下:
而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行。
当我们想将指定的任务提交到Fork/join框架中进行执行的时候,就需要任务所在类继承ForkJoinTask抽象类来表示一个ForkJoin任务,但是目前我们并不需要继承ForkJoinTask抽象类,JDK给我们提供了他的两个子类:
类名 | 描述 |
---|---|
RecursiveAction | 重写compute抽象方法,没有返回结果。 |
RecursiveTask | 重写compute抽象方法,有返回结果。 |
public abstract class RecursiveAction extends ForkJoinTask<Void> {
private static final long serialVersionUID = 5232453952276485070L;
protected abstract void compute();
public final Void getRawResult() { return null; }
protected final void setRawResult(Void mustBeNull) { }
protected final boolean exec() {
compute();
return true;
}
}
public abstract class RecursiveTask<V> extends ForkJoinTask<V> {
private static final long serialVersionUID = 5232453952276485270L;
protected abstract V compute();
public final V getRawResult() {
return result;
}
protected final void setRawResult(V value) {
result = value;
}
protected final boolean exec() {
result = compute();
return true;
}
}
Fork/Join的任务需要通过ForkJoinPool来进行执行。
ForkJoinPool由ForkJoinTask数组和ForkJoinWorkerThread数组组成,ForkJoinTask数组负责存放程序提交给ForkJoinPool的任务,而ForkJoinWorkerThread数组负责开创相应数量的线程执行这些任务。
无参构造
public ForkJoinPool() {
this(Math.min(MAX_CAP, Runtime.getRuntime().availableProcessors()),
defaultForkJoinWorkerThreadFactory, null, false,
0, MAX_CAP, 1, null, DEFAULT_KEEPALIVE, TimeUnit.MILLISECONDS);
}
有一个参数的构造方法
public ForkJoinPool(int parallelism) {
this(parallelism, defaultForkJoinWorkerThreadFactory, null, false,
0, MAX_CAP, 1, null, DEFAULT_KEEPALIVE, TimeUnit.MILLISECONDS);
}
全参构造
public ForkJoinPool(int parallelism,
ForkJoinWorkerThreadFactory factory,
UncaughtExceptionHandler handler,
boolean asyncMode,
int corePoolSize,
int maximumPoolSize,
int minimumRunnable,
Predicate<? super ForkJoinPool> saturate,
long keepAliveTime,
TimeUnit unit) {
if (parallelism <= 0 || parallelism > MAX_CAP ||
maximumPoolSize < parallelism || keepAliveTime <= 0L)
throw new IllegalArgumentException();
// 此处已省略复杂代码
int n = (parallelism > 1) ? parallelism - 1 : 1; // at least 2 slots
n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16;
n = (n + 1) << 1; // power of two, including space for submission queues
this.workQueues = new WorkQueue[n];
// 省略复杂代码
}
parallelism
参数直接影响着this.workQueues
工作线程的大小。 Math.min(MAX_CAP, Runtime.getRuntime().availableProcessors())
static final int MAX_CAP = 0x7fff; // 32767
parallelism
参数假设为2,那么池的并行度级别为 2。这意味着池将使用 2 个处理器内核,但是不能超过MAX_CAP,从全参构造可以看出,超过MAX_CAP后就会报错。当新的任务到来,线程池会通知其他线程前去处理,如果这时没有处于等待的线程或者处于活动的线程非常少,就会往线程池中添加线程。
/**
在增长ctl数量之前尝试添加一个工作线程,依靠creatWorker方法进行创建并退出。
**/
private void tryAddWorker(long c) {
do {
long nc = ((RC_MASK & (c + RC_UNIT)) | (TC_MASK & (c + TC_UNIT)));
if (ctl == c && CTL.compareAndSet(this, c, nc)) {
createWorker();
break;
}
} while (((c = ctl) & ADD_WORKER) != 0L && (int)c == 0);
}
/**
利用ForkJoinWorkerThreadFactory创建工作线程。
1、如果成功创建并返回true。
2、如果ForkJoinWorkerThreadFactory为NULL并且创建的线程为NULL,
则调用deregisterWorker方法取消工作线程的注册,最后返回false。
**/
private boolean createWorker() {
ForkJoinWorkerThreadFactory fac = factory;
Throwable ex = null;
ForkJoinWorkerThread wt = null;
try {
if (fac != null && (wt = fac.newThread(this)) != null) {
wt.start();
return true;
}
} catch (Throwable rex) {
ex = rex;
}
deregisterWorker(wt, ex);
return false;
}
执行方式 | 描述 |
---|---|
void execute(ForkJoinTask> task) | 异步执行tasks,无返回值 |
异步执行,且带Task返回值,可通过task.get 实现同步到主线程 | |
tasks会被同步到主线程,主线程和ForkJoin线程是异步执行,而invoke启动时,主线程同步等待ForkJoin线程执行。 |
我们在使用forkJoin的时候一定要注意pool的启动方式,不然就会有隐藏的坑。