并发-CountedCompleter详解

    CountedCompleter是ForkJoinTask的的子类,对ForkJoinTask做了一下扩展

    CountedCompleter有以下特点:

  • CountedCompleter维护两个动作:完成动作(onCompletion)和异常动作(onExceptionalCompletion),开发者可以重写这两个方法以实现自定义的动作内容;
  • CountedCompleter维护一个父任务变量(completer),当前任务的完成情况、异常可通过这个变量逐级传递上去;
  • CountedCompleter维护挂起任务(子任务)的计数(pending),挂起任务完成会触发父任务的pending减1,父任务pending计数减到0触发父任务完成动作(onCompletion);
  • CountedCompleter依旧是个抽象类,核心的方法是compute,开发者需要自定这个方法,注意compute方法返回之前需要调用tryComplete;
  • 作为一个ForkJoinTask,compute返回之后ForkJoinPool并没有将ForkJoinTask的状态置为Normal,如果此时调用join会阻塞线程,开发者可以在onCompletion调用this.quietlyComplete()设置Normal。

    因为CountedCompleter这些特点,CountedCompleter方便将大任务逐级拆成子任务(subtask),直到最小单元(leaf task即叶子任务),叶子任务完成后逐级传递状态及结果。

    CountedCompleter有一点比较不同,CountedCompleter执行完compute之后就运行结束,并没有等待子任务(如果有fork任务)结束。子任务完成后子任务调用tryComplete逐级判断任务是否完成。也就是说一个根任务fork出来的所有任务(无论是否叶子任务)都是并行的,ForkJoinPool调度执行是无区别对待的。所以,CountedCompleter调用compute并不会设置任务为Normal,因为如果设置Normal,调用join()就会立即返回,但此时子任务可能都还没有执行。源码如下(exec返回false,ForkJoinPool就不会设置其状态为Normal):

public abstract class CountedCompleter<T> extends ForkJoinTask<T> {
    ......
    //返回false,ForkJoinPool就不会设置任务完成
    protected final boolean exec() {
        compute();
        return false;
    }
    ......
}

    CountedCompleter使用方法:就是在compute里面如果符合fork条件就fork子任务,如果不符合就执行具体的任务逻辑,最后调用tryComplete。tryComplete判断子任务是否都已经完成(pending==0)。如果是则执行onCompletion并传递到父任务执行相同逻辑;如果不是则将pending-1。

public abstract class CountedCompleter<T> extends ForkJoinTask<T> {
    ......
    public final void tryComplete() {
        CountedCompleter<?> a = this, s = a;
        for (int c;;) {
            if ((c = a.pending) == 0) {
                a.onCompletion(s);
                //笔者注:如果当前任务完成(pending==0),执行完当前任务的完成动作(onCompletion),传递到父任务
                if ((a = (s = a).completer) == null) {
                    s.quietlyComplete();
                    return;
                }
            }
            //笔者注:当前任务没有完成(pending!=0),将pending-1
            else if (U.compareAndSwapInt(a, PENDING, c, c - 1))
                return;
        }
    }
    ......
}

一般来讲最后一个任务不调用fork而是直接调用该任务的compute方法,让当前线程继续执行最后一个子任务。因为线程在执行完最后一个任务的fork之后除了调用tryComplete没有执行任何实际逻辑。为了少一次线程调度,开发者可以直接让当前线程执行最后一个子任务(调用compute而不是fork),此时pending要少设置1。例如,根任务R的pending设置为2,运行根任务的线程直接调用叶子任务L2的compute;分支任务F1的pending设置为1,运行分支任务F1的线程直接调用叶子任务L4的compute。具体的使用方法CountedCompleter的作者Doug在类文件中有说明。

 

ForkJoin框架详解

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值