CountedCompleter将结果合并从任务分解计算的过程中分离出来,只要每个子任务能保存状态,那么通过合并逻辑,就可以在任何时候将结果合并。
CountedCompleter中如何知道子任务计算完成?它使用了一个孩子兄弟表示的树形结构。用一个变量统计当前一个节点的子节点个数,兄弟节点通过判断计算器可以知道其他兄弟任务是否完成,如果没有完成就将计数器减一表示自己已经完成。如果完成了就将所有兄弟任务的结果合并(合并顺序可以自定义),然后递归判断父节点的情况。这样最终就可以将任务结果合并到根节点。
因此只要理解CountedCompleter的执行流程,就可以知道使用CountedCompleter其实是很简单的:
1、创建子任务,创建子任务首先将父任务的pending计数器加1,然后保存子任务到兄弟节点可以访问到的地方,以便之后能进行合并。
2、任务完成,任务完成时调用tryComplete合并结果,(可以合并时会调用onCompletion合并结果)
CountedCompleter除了可以像上面这种mapreduce的使用方法之外也有其他使用场景,例如不需要结果、find Any等,实现方式都大致相同。
下面就是代码环节,直接使用源码上的例子加上一些自己的注释理解。
无结果模板,这里通过直接计算子任务的数量一次性设置了父任务的pending值。
public static <E> void forEach(E[] array, Consumer<E> action) {
// 匿名类
class Task extends CountedCompleter<Void> {
final int lo, hi;
Task(Task parent, int lo, int hi) {
super(parent, 31 - Integer.numberOfLeadingZeros(hi - lo)); // logn
this.lo = lo; this.hi = hi;
}
public void compute() {
for (int n = hi - lo; n >= 2; n /= 2)
new Task(this, lo + n/2, lo + n).fork(); // 提交子任务
action.accept(array[lo]);
propagateCompletion();
}
}
if (array.length > 0)
new Task(null, 0, array.length).invoke(); // 任务入口
}
findany模板
class Searcher<E> extends CountedCompleter<E> {
final E[] array; final AtomicReference<E> result; final int lo, hi;
Searcher(CountedCompleter<?> p, E[] array, AtomicReference<E> result, int lo, int hi) {
super(p);
this.array = array; this.result = result; this.lo = lo; this.hi = hi;
}
public E getRawResult() { return result.get(); }
public void compute() { // similar to ForEach version 3
int l = lo, h = hi;
while (result.get() == null && h >= l) {
if (h - l >= 2) {
int mid = (l + h) >>> 1;
addToPendingCount(1); // 创建右子任务,父任务计数器加1
new Searcher(this, array, result, mid, h).fork(); // 提交右子任务
h = mid; // 当前线程计算左子任务,MapReduce里面是递归写法,这里是循环写法
}
else {
E x = array[l];
if (matches(x) && result.compareAndSet(null, x))
quietlyCompleteRoot(); // 找到解,设置结果,并且设置任务为完成状态,用户根据通过根节点获得解
break;
}
}
tryComplete(); // 任务完成,没有解也得完成啊
}
boolean matches(E e) { ... } // return true if found
public static <E> E search(E[] array) {
return new Searcher<E>(null, array, new AtomicReference<E>(), 0, array.length).invoke();
}
}
mapreduce的示例,通过递归实现只有两个子任务
class MyMapper<E> { E apply(E v) { ... } }
class MyReducer<E> { E apply(E x, E y) { ... } }
class MapReducer<E> extends CountedCompleter<E> {
final E[] array; final MyMapper<E> mapper;
final MyReducer<E> reducer; final int lo, hi;
MapReducer<E> sibling; // 将相邻的节点当成兄弟节点,只有两个子任务
E result; // 结果
MapReducer(CountedCompleter<?> p, E[] array, MyMapper<E> mapper,
MyReducer<E> reducer, int lo, int hi) {
super(p);
this.array = array; this.mapper = mapper;
this.reducer = reducer; this.lo = lo; this.hi = hi;
}
public void compute() {
if (hi - lo >= 2) {
int mid = (lo + hi) >> 1;
MapReducer<E> left = new MapReducer(this, array, mapper, reducer, lo, mid);
MapReducer<E> right = new MapReducer(this, array, mapper, reducer, mid, hi);
left.sibling = right;
right.sibling = left;
setPendingCount(1); // 创建子任务父任务的pending计数器加1
right.fork(); // 提交右子任务
left.compute(); // 在当前线程计算左子任务
}
else {
if (hi > lo)
result = mapper.apply(array[lo]);
tryComplete(); // 叶子节点完成,尝试合并其他兄弟节点的结果,会调用onCompletion方法
}
}
public void onCompletion(CountedCompleter<?> caller) {
if (caller != this) {
MapReducer<E> child = (MapReducer<E>)caller;
MapReducer<E> sib = child.sibling;
// 合并子任务结果,只有两个子任务
if (sib == null || sib.result == null)
result = child.result;
else
result = reducer.apply(child.result, sib.result);
}
}
public E getRawResult() { return result; }
public static <E> E mapReduce(E[] array, MyMapper<E> mapper, MyReducer<E> reducer) {
return new MapReducer<E>(null, array, mapper, reducer,
0, array.length).invoke();
}
}}
MapReduce模板,通过循环实现有多个子任务
class MapReducer<E> extends CountedCompleter<E> { // version 2
final E[] array; final MyMapper<E> mapper;
final MyReducer<E> reducer; final int lo, hi;
MapReducer<E> forks, next; // 保存多个子任务
E result;
MapReducer(CountedCompleter<?> p, E[] array, MyMapper<E> mapper,
MyReducer<E> reducer, int lo, int hi, MapReducer<E> next) {
super(p);
this.array = array; this.mapper = mapper;
this.reducer = reducer; this.lo = lo; this.hi = hi;
this.next = next;
}
public void compute() {
int l = lo, h = hi;
while (h - l >= 2) {
int mid = (l + h) >>> 1;
addToPendingCount(1); // 创建子任务,父任务计数器加1
(forks = new MapReducer(this, array, mapper, reducer, mid, h, forks)).fork(); // 提交子任务
h = mid; // 循环处理左区间
}
if (h > l)
result = mapper.apply(array[l]);
// 按照子任务链合并兄弟节点,然后再合并父节点那一层的结果,直到合并完全部结果。和tryComplete()+onCompletion()的作用一样
for (CountedCompleter<?> c = firstComplete(); c != null; c = c.nextComplete()) {
for (MapReducer t = (MapReducer)c, s = t.forks; s != null; s = t.forks = s.next)
t.result = reducer.apply(t.result, s.result);
}
}
public E getRawResult() { return result; }
public static <E> E mapReduce(E[] array, MyMapper<E> mapper, MyReducer<E> reducer) {
return new MapReducer<E>(null, array, mapper, reducer,
0, array.length, null).invoke();
}
}