前言
Fork-Join将一个大任务分解成多个子任务,分别进行处理,最后再将处理结果合并。
双端队列与工作密取(Work Stealing)
Fork-Join用到的模式 -工作密取
Java6 增加了两种容器类型,Deque (发 音 为 “deck”)和 BlockingDeque, 它们分别对 Queue和 BlockingQueue进行了扩展。Deque是一个双端队列,实现了在队列头和队列尾的高效插入和移除。具体实现包括ArrayDeque和 LinkedBlockingDeque。
正如阻塞队列适用于生产者-消费者模式,双端队列同样适用于另一种相关模式,即工作密取 (Work Stealing)。在生产者-消费者设计中,所有消费者有一个共享的工作队列,而在工作密取设计中,每个消费者都有各自的双端队列。如果一个消费者完成了自己双端队列中的全部工作,那么它可以从其他消费者双端队列末尾秘密地获取工作。密取工作模式比传统的生 产者-消费者模式具有更高的可伸缩性,这是因为工作者线程不会在单个共享的任务队列上发 生竞争。在大多数时候,它们都只是访问自己的双端队列,从而极大地减少了竞争。当工作者 线程需要访问另一个队列时,它会从队列的尾部而不是从头部获取工作,因此进一步降低了队列上的竞争程度。
工作密取非常适用于既是消费者也是生产者问题— 当执行某个工作时可能导致出现更多 的工作。例如,在网页爬虫程序中处理一个页面时,通常会发现有更多的页面需要处理。类似的还有许多搜索图的算法,例如在垃圾回收阶段对堆进行标记,都可以通过工作密取机制来实 现髙效并行。当一个工作线程找到新的任务单元时,它会将其放到自己队列的末尾(或者在工作共享设计模式中,放入其他工作者线程的队列中)。当双端队列为空时,它会在另一个线程的队列队尾查找新的任务,从而确保每个线程都保持忙碌状态。
Fork-Join介绍
Fork-Join使用工作密取模式来完成并行任务执行。如果是我们来设计算法应该如何?
1.分割任务。把大任务分割成子任务,有可能子任务还是很大,所以还需要不停的分割,直到分割出的子任务足够小
2.执行任务并合并结果。分割的子任务分别放在双端队列里,然后几个启动线程分别从双端队列里获取任务执行。子任务执行完的结果都统一放在一个队列里,启动一个线程从队列里拿数据,然后合并这些数据。
Fork/Join使用两个类来完成以上两件事情:
- ForkJoinTask:我们要使用ForkJoin框架,必须首先创建一个ForkJoin任务。它提供在任务中执行fork()和join()操作的机制,通常情况下我们不需要直接继承ForkJoinTask类,而只需要继承它的子类,Fork/Join框架提供了以下两个子类:
RecursiveAction:用于没有返回结果的任务。
RecursiveTask :用于有返回结果的任务。
- ForkJoinPool :ForkJoinTask需要通过ForkJoinPool来执行,任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部。当一个工作线程的队列里暂时没有任务时,它会随机从其他工作线程的队列的尾部获取一个任务。
Fork-Join使用
需求:向数组装入一千万次随机数生成结果,并统计其中大于0.5的个数
public class ForkJoinTest {
public static void main(String[] args) {
final int SIZE = 10000000;//一千万次随机
double[] numbers = new double[SIZE];
for(int i =0;i<SIZE;i++) numbers[i] = Math.random();
Counter counter = new ForkJoinTest().new Counter(numbers, 0, numbers.length, x -> x > 0.5); //
ForkJoinPool pool = new ForkJoinPool();
pool.invoke(counter);
System.out.println(counter.join()); //5001344
}
class Counter extends RecursiveTask<Integer>{
/**
*
*/
private static final long serialVersionUID = 1L;
public static final int THRESHOLD = 1000;//阀门值 分割次数
private double[] values;
private int from;
private int to;
private DoublePredicate filter; //?
public Counter(double[] values, int from, int to, DoublePredicate filter){
this.values = values;
this.from = from;
this.to = to;
this.filter = filter;
}
@Override
protected Integer compute() {
if(to - from < THRESHOLD){
int count = 0;
for(int i = from;i<to;i++){
if(filter.test(values[i])) count++;
}
return count;
}else{
int mid = (from + to)/2;
Counter first = new Counter(values,from,mid,filter);
Counter second = new Counter(values,mid,to,filter);
invokeAll(first,second);//接受任务并阻塞
return first.join() + second.join();//生成结果
}
}
}
}
源码
看看ForkJoinTask的fork方法,向ForkJoinWorkerThread(当前线程)的workQueue(支持工作窃取和外部任务提交的队列)放入任务
public final ForkJoinTask<V> fork() {
Thread t;
if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
((ForkJoinWorkerThread)t).workQueue.push(this);
else
ForkJoinPool.common.externalPush(this);
return this;
}
ForkJoinPool.WorkQueue的push方法,ForkJoinPool的signalWork()方法唤醒或创建一个工作线程来执行任务
final void push(ForkJoinTask<?> task) {
ForkJoinTask<?>[] a; ForkJoinPool p;
int b = base, s = top, n;
if ((a = array) != null) { // ignore if queue removed
int m = a.length - 1; // fenced write for task visibility
U.putOrderedObject(a, ((m & s) << ASHIFT) + ABASE, task);
U.putOrderedInt(this, QTOP, s + 1);
if ((n = s - b) <= 1) {
if ((p = pool) != null)
p.signalWork(p.workQueues, this);
}
else if (n >= m)
growArray();
}
}
ForkJoinTask的join方法。join方法的主要作用是阻塞当前线程并等待获取结果。使用doJoin()方法执行,并方法处理结果状态
public final V join() {
int s;
if ((s = doJoin() & DONE_MASK) != NORMAL)
reportException(s);
return getRawResult();
}
doJoin()方法 。在doJoin()方法里,首先通过查看任务的状态,看任务是否已经执行完了,如果执行完了,则直接返回任务状态,如果没有执行完,则从任务数组里取出任务并执行。如果任务顺利执行完成了,则设置任务状态为NORMAL,如果出现异常,则纪录异常,并将任务状态设置为EXCEPTIONAL。
private int doJoin() {
int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
return (s = status) < 0 ? s :
((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
(w = (wt = (ForkJoinWorkerThread)t).workQueue).
tryUnpush(this) && (s = doExec()) < 0 ? s :
wt.pool.awaitJoin(w, this, 0L) :
externalAwaitDone();
}
小结
java原生fork-join框架运用上不普遍但给我们提供了一种并行计算的思路