java7开始,concurrent包下新增Fork/Join框架,用于并行执行任务,它把大任务切分成多个小任务,最后汇集每个小任务的计算结果,从这点功能上来说,和java里的异步任务FutureTask有些许的类似。例如导出功能,要一次导出50个大文件,此时就可以使用Fork/Join框架来切分任务,分成10个小任务,每批导5个文件,一方面变常规的串行化方式为并行方式,另一方面也极大的加快任务的执行速度。使用fork/join框架会一直切分大任务为小任务,直到不能切分为止。
Fork/Join框架使用
-
创建ForkJoin任务(主要包含fork()、join()方法),继承ForkJoinTask的子类
-
通过ForkJoinPool(池)执行ForkJoinTask
其中ForkJoinTask的子类RecursiveAction用于没有返回结果的任务,RecursiveTask则用于有返回值的任务。如下模拟求和功能
public class SumTask extends RecursiveTask<Integer> {
private static final int THRESHOLD =3; //设定一个阈值
Integer start;
Integer end;
public SumTask(Integer start, Integer end) {
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
// 如果任务小于阈值,说明任务足够小,不切分直接计算
int sum = 0;
if (end - start <= THRESHOLD) {
for (int i = start; i <= end; i++) {
sum += i;
}
return sum;
}
// 超过阈值
int middle = (end + start) / 2;
SumTask subtask1 = new SumTask(start, middle);
SumTask subtask2 = new SumTask(middle+1, end);
invokeAll(subtask1, subtask2);
Integer result1 = subtask1.join();
Integer result2 = subtask2.join();
//合并任务
sum = result1 + result2;
System.out.println("result1+result2"+"="+sum);
return sum;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ForkJoinPool joinPool = new ForkJoinPool();
List<Integer> list = new ArrayList<Integer>();
for (int i = 1; i <=10; i++) {
list.add(i); //size=10
}
SumTask task = new SumTask(1,10);
ForkJoinTask<Integer> result = joinPool.submit(task);
Integer integer = result.get();
System.out.println(integer);
}
}
输出结果如下,可以看到,在计算10以内的求和运算时,fork/join拆分为了 1-5、6-10两个任务,进而合并和这两个任务的计算结果
result1+result2=15
result1+result2=40
result1+result2=55
55
主要的逻辑都在compute方法中,通过阈值的判断任务是否足够小而进一步决定是否切分,足够小就直接执行,否则就切分为两个子任务,每个子任务在进行fork方法时,又进入compute方法看子任务是否符合继续切分条件,如果不需要切分,则执行子任务并返回result,join方法可以拿到子任务的计算结果。
fork/join框架的异常捕获
由于任务在执行过程中无法直接捕获到主线程中抛出的异常,fork/join框架提供了isCompletedAbnormally()方法来判断任务是否被取消或抛出异常,并通过getException()方法获取异常
if(task.isCompletedAbnormally()){
System.out.println(task.getException());
}
当任务无异常或没有完成,getException()方法返回null;若任务被取消,则返回CancellationException
fork/join实现原理
RecursiveAction类或者RecursiveTask继承自ForkJoinTask类,其关键方法为fork()和join方法;而执行fork/join框架的执行者-ForkJoinPool,其中的主要对象为ForkJoinWorkerThread数组和ForkJoinTask数组,ForkJoinTask负责将任务提交给ForkJoinPool,ForkJoinWorkerThread则负责执行这些任务。
fork方法
pushTask方法往ForkJoinTask数组里放任务--->signalWork方法唤醒或创建线程去执行任务-->
public final ForkJoinTask<V> fork() {
((ForkJoinWorkerThread) Thread.currentThread())
.pushTask(this);
return this;
}
final void pushTask(ForkJoinTask<?> t) {
ForkJoinTask<?>[] q; int s, m;
if ((q = queue) != null) { // ignore if queue removed
long u = (((s = queueTop) & (m = q.length - 1)) << ASHIFT) + ABASE;
UNSAFE.putOrderedObject(q, u, t);
queueTop = s + 1; // or use putOrderedInt
if ((s -= queueBase) <= 2)
pool.signalWork();
else if (s == m)
growQueue();
}
}
final void signalWork() {
/*
* The while condition is true if: (there is are too few total
* workers OR there is at least one waiter) AND (there are too
* few active workers OR the pool is terminating). The value
* of e distinguishes the remaining cases: zero (no waiters)
* for create, negative if terminating (in which case do
* nothing), else release a waiter. The secondary checks for
* release (non-null array etc) can fail if the pool begins
* terminating after the test, and don't impose any added cost
* because JVMs must perform null and bounds checks anyway.
*/
long c; int e, u;
while ((((e = (int)(c = ctl)) | (u = (int)(c >>> 32))) &
(INT_SIGN|SHORT_SIGN)) == (INT_SIGN|SHORT_SIGN) && e >= 0) {
if (e > 0) { // release a waiting worker
int i; ForkJoinWorkerThread w; ForkJoinWorkerThread[] ws;
if ((ws = workers) == null ||
(i = ~e & SMASK) >= ws.length ||
(w = ws[i]) == null)
break;
long nc = (((long)(w.nextWait & E_MASK)) |
((long)(u + UAC_UNIT) << 32));
if (w.eventCount == e && UNSAFE.compareAndSwapLong(this, ctlOffset, c, nc)) {
w.eventCount = (e + EC_UNIT) & E_MASK;
if (w.parked)
UNSAFE.unpark(w);
break;
}
}
else if (UNSAFE.compareAndSwapLong
(this, ctlOffset, c,
(long)(((u + UTC_UNIT) & UTC_MASK) |
((u + UAC_UNIT) & UAC_MASK)) << 32)) {
addWorker();
break;
}
}
}
join方法
doJoin方法判断当前任务执行状态-->已取消:返回CancellationException异常;已完成:直接返回计算结果;任务发生异常:抛出相对应的异常信息。所以总的来说,join方法的作用就是阻塞线程来等待任务的完成。
public final V join() {
if (doJoin() != NORMAL)
return reportResult();
else
return getRawResult();
}
private int doJoin() {
Thread t; ForkJoinWorkerThread w; int s; boolean completed;
if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) {
if ((s = status) < 0)
return s;
if ((w = (ForkJoinWorkerThread)t).unpushTask(this)) {
try {
completed = exec();
} catch (Throwable rex) {
return setExceptionalCompletion(rex);
}
if (completed)
return setCompletion(NORMAL);
}
return w.joinTask(this);
}
else
return externalAwaitDone();
}