java中的ForkJoinPool(二)

1,081 阅读6分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

接着聊ForkJoinPool,本篇就来看看其源码。

ForkJoinTask任务状态

/** The run status of this task */
volatile int status; // accessed directly by pool and workers
static final int DONE_MASK   = 0xf0000000;  // mask out non-completion bits
static final int NORMAL      = 0xf0000000;  // must be negative
static final int CANCELLED   = 0xc0000000;  // must be < NORMAL
static final int EXCEPTIONAL = 0x80000000;  // must be < CANCELLED
static final int SIGNAL      = 0x00010000;  // must be >= 1 << 16
static final int SMASK       = 0x0000ffff;  // short bits for tags 

statue为ForkJoinTask的状态,初始值为0,标识正则处理任务状态;NORMAL:正常状态,标识任务正常结束;CANCELLED:标识任务被取消;EXCEPTIONAL:标识任务执行异常;SIGNAL: 通知状态,有其他任务依赖当前任务,任务结束前,通知其他任务join当前任务的结果。

ForkJoinTask

ForkJoinTask是Futrue接口的子类,作用就是根据任务的分解实现,将任务进行拆分,以及等待子任务的执行结果合并成父任务的结果。ForkJoinTask内部存在一个整数类型的成员status,该成员高16位记录任务的执行状态,如:如NORMAL、CANCELLED或EXCEPTIONAL,低16位预留用于记录用户自定义的任务标签。源码如下:

public abstract class ForkJoinTask<V> implements Future<V>, Serializable { 
    private static final ExceptionNode[] exceptionTable;        
    private static final ReentrantLock exceptionTableLock;
    private static final ReferenceQueue<Object> exceptionTableRefQueue; 

   //固定容量的exceptionTable(代表数组长度为32,下标存储链表头节点)
    private static final int EXCEPTION_MAP_CAPACITY = 32;

    static final class ExceptionNode extends WeakReference<ForkJoinTask<?>> {
        final Throwable ex;
        ExceptionNode next;
        final long thrower;  // 抛出异常的线程id
        final int hashCode;  // 在弱引用消失之前存储hashCode
        ExceptionNode(ForkJoinTask<?> task, Throwable ex, ExceptionNode next) {
            // //在ForkJoinTask被GC回收之后,会将该节点加入队列exceptionTableRefQueue
            super(task, exceptionTableRefQueue); 
            this.ex = ex;
            this.next = next;
            this.thrower = Thread.currentThread().getId();
            this.hashCode = System.identityHashCode(task);
        }
    }

    // 任务执行完成后返回结果,未完成返回null
    public abstract V getRawResult();
    // 强制性的给定返回结果
    protected abstract void setRawResult(V value);
    // 如果执行过程抛出异常则记录捕获的异常并更改任务状态为EXCEPTIONAL
    // 如果执行正常结束,设置任务状态为NORMAL正常结束状态
    // 如果当前是子任务,设置为SIGNAL状态并通知其他需要join该任务的线程
    protected abstract boolean exec();
    
    // 阻塞等待任务执行结果
    public final V get();
    // 在给定时间内等待返回结果,超出给定时间则中断线程
    public final V get(long timeout, TimeUnit unit);
    // 阻塞非工作线程直至任务结束或者中断(该过程可能会发生窃取动作),返回任务的status值
    private int externalInterruptibleAwaitDone();
    // 尝试取消任务,成功返回true,反之false
    public boolean cancel(boolean mayInterruptIfRunning);
    // 判断任务是否已执行结束
    public final boolean isDone();
    // 判断任务是否被取消
    public final boolean isCancelled();
    
    // 执行任务
    final int doExec();
    // 修改任务状态
    private int setcompletion (int completion);
    // 取消任务
    public boolean cancel(boolean mayInterruptIfRunning);
    
    // 将新创建的子任务放入当前线程的工作队列
    public final ForkJoinTask<V> fork();

    public final V join();

    private int doJoin();
    // 执行任务,正常结束则返回结果,异常结束则报告异常
    public final V invoke();

    private int doInvoke();
    // 阻塞线程直至任务执行结束,如果未执行完成,外部线程尝试帮助执行
    private int externalAwaitDone();

    public static void invokeAll(ForkJoinTask<?> t1, ForkJoinTask<?> t2);

    public static void invokeAll(ForkJoinTask<?>... tasks);

    public static <T extends ForkJoinTask<?>> 
        Collection<T> invokeAll(Collection<T> tasks);
    
    // 记录异常信息以及设置任务状态
    final int recordExceptionalCompletion(Throwable ex);

    private void clearExceptionalCompletion();

    private static void expungeStaleExceptions();

    private Throwable getThrowableException();
}

ForkJoinTask内部成员包括表示任务状态的status,其他的成员则都是跟任务异常信息记录相关的。

fork()、join()

ForkJoinTask的主要方法有执行方法fork(),join(),执行任务方法invoke系列。

// fork方法
public final ForkJoinTask<V> fork() {
    Thread t;
    
    //如果线程类型为ForkJoinWorkerThread,则将任务推入workQueue进行处理,
    //否则,交由ForkJoinPool的common线程池进行处理
    if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
        ((ForkJoinWorkerThread)t).workQueue.push(this);
    else
        ForkJoinPool.common.externalPush(this);
    return this;
}

// join方法
public final V join() {
    int s;
    
    //调用doJoin()进行任务的执行,若任务结果为非正常完成,则根据状态抛出不同的异常,
    //如若状态为CANCELLED,则抛出CancellationException(),异常;
    //若状态为EXCEPTIONAL,则抛出包装后的异常
    if ((s = doJoin() & DONE_MASK) != NORMAL)
        reportException(s);
    return getRawResult();
}

// doJoin方法
private int doJoin() {
    int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
    // status<0则直接返回status值
    return (s = status) < 0 ? s :
      // 若执行任务的当前线程类型为ForkJoinWorkerThread,且将任务从线程的工作队列中移除成功则调用doExec()执行任务,若任务执行状态为正常结束,则返回状态,否则awaitJoin()等待任务结束。
        ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
        (w = (wt = (ForkJoinWorkerThread)t).workQueue).
        tryUnpush(this) && (s = doExec()) < 0 ? s :
        wt.pool.awaitJoin(w, this, 0L) :
      // 不是则调用externalAwaitDone()等待任务执行完成
        externalAwaitDone();
}

fork方法处理过程:先判断当前线程是否为池中的工作线程类型 是:将当前任务压入当前线程的任务队列中;不是:将当前任务压入common池中某个工作线程的任务队列中。

doJoin&join方法判断有点多: 先判断任务状态status是否小于0:

小于:任务正常完成,返回status值

不小于:判断当前线程是否为当前线程类型为ForkJoinWorkerThread:

是:取出线程任务队列的当前task执行,判断执行是否结束:

结束:返回执行结束的status值

未结束:调用awaitJoin方法等待任务结束

不是:调用externalAwaitDone()方法等待任务执行完成

invoke源码

再看一下invoke源码,ForkJoinPool线程池执行。提交任务的方式有三种:invoke()、execute()以及submit(),源码如下:

public final V invoke() {
    int s;
    //执行任务并返回状态,处理同doJoin()类似
    if ((s = doInvoke() & DONE_MASK) != NORMAL)
        reportException(s);
    return getRawResult();
}


private int doInvoke() {
    int s; Thread t; ForkJoinWorkerThread wt;
    //执行任务并获取任务状态,状态<0表示正常完成,否则等待任务完成
    return (s = doExec()) < 0 ? s :
        ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
        (wt = (ForkJoinWorkerThread)t).pool.
        awaitJoin(wt.workQueue, this, 0L) :
        externalAwaitDone();
}

//处理两个任务
public static void invokeAll(ForkJoinTask<?> t1, ForkJoinTask<?> t2) {
    int s1, s2;
    //提交任务t2,交由线程池执行
    t2.fork();
    //执行任务t1并获取直接结果的任务状态
    if ((s1 = t1.doInvoke() & DONE_MASK) != NORMAL)
        t1.reportException(s1);
    //获取任务t2的直接结果状态
    if ((s2 = t2.doJoin() & DONE_MASK) != NORMAL)
        t2.reportException(s2);
}

//处理多个任务
public static void invokeAll(ForkJoinTask<?>... tasks) {
    Throwable ex = null;
    int last = tasks.length - 1;
    for (int i = last; i >= 0; --i) {
        ForkJoinTask<?> t = tasks[i];
        //任务为空则跑NPE异常
        if (t == null) {
            if (ex == null)
                ex = new NullPointerException();
        }
        //非最后一个任务,则推入线程池执行
        else if (i != 0)
            t.fork();
        //最后一个任务直接调用doInvoke()执行    
        else if (t.doInvoke() < NORMAL && ex == null)
            ex = t.getException();
    }
    //遍历任务,获取任务执行结果
    for (int i = 1; i <= last; ++i) {
        ForkJoinTask<?> t = tasks[i];
        if (t != null) {
            //若有某个任务执行有异常,则取消所有任务
            if (ex != null)
                t.cancel(false);
            //获取任务执行结果,若结果非正常结束,获取异常结果    
            else if (t.doJoin() < NORMAL)
                ex = t.getException();
        }
    }
    if (ex != null)
        rethrow(ex);
}

WorkQueue源码

WorkQueue为ForkJoinPool的工作队列,其封装提交的任务ForkJoinTask、线程池ForkJoinPool、执行线程ForkJoinWorkerThread、及其他任务相关数据等。每个执行者ForkJoinWorkerThread对象中存在各自的工作队列,ForkJoinTask被存储在工作队列中,而ForkJoinPool使用一个WorkQueue数组管理协调所有执行者线程的队列。源码如下:

volatile int scanState;    
int stackPred;             
int nsteals;               
int hint;                 
int config;                
volatile int qlock;       
volatile int base;         
int top;                   
ForkJoinTask<?>[] array;   
final ForkJoinPool pool;  
final ForkJoinWorkerThread owner; 
volatile Thread parker;    
volatile ForkJoinTask<?> currentJoin;  
volatile ForkJoinTask<?> currentSteal; 

scanState:如果WorkQueue没有属于自己的owner(下标为偶数的都没有),该值为 inactive 也就是一个负数;如果有自己的owner,该值的初始值为其在WorkQueue[]数组中的下标,也肯定是个奇数;如果这个值,变成了偶数,说明该队列所属的Thread正在执行Task。

stackPred:前一个栈顶的ctl值,由此构成一个栈;

int nsteals:窃取任务的数量统计;

int hint:用于记录随机选择窃取任务,被窃取任务workQueue在数组中的下标值;

config:记录当前队列在数组中的下标和工作模式,高十六位记录工作模式,低十六位记录数组下标。如果下标为偶数的WorkQueue,则其mode是共享类型。如果有自己的owner 默认是 LIFO;

qlock: 锁标识(类似于AQS是state锁标识),使用此标识抢占锁。为1表示队列被锁定,为0表示未锁定,小于0代表当前队列注销或线程池关闭(terminate状态时为-1)

base:worker steal的偏移量(下一个pool操作的索引值(栈底/队列头部))。

top:owner执行任务的偏移量(下一个push操作的索引值(栈顶/队列尾部))。

array:存放任务的数组,初始化时不会分配空间,采用懒加载形式初始化空间;

pool:所属线程池的引用指向;

owner:当前队列所属线程的引用,如果为外部提交任务的共享队列则为null;

parker:如果 owner 挂起,则使用该变量做记录挂起owner的线程。

currentJoin: 当前正在join等待结果的任务。

currentSteal:当前执行的任务是steal过来的任务,该变量做记录。

static final class WorkQueue {
    // 构造器
    WorkQueue(ForkJoinPool pool, ForkJoinWorkerThread owner) {
        this.pool = pool;
        this.owner = owner;
        // 开始的时候都指向栈顶
        base = top = INITIAL_QUEUE_CAPACITY >>> 1;
    }
    
    // 添加方法:将一个任务添加进队列中
    // 注意:索引不是通过数组下标计算的,而是通过计算内存偏移量定位
    final void push(ForkJoinTask<?> task);
    // 扩容方法:队列元素数量达到容量时,扩容两倍并移动元素到新数组
    final ForkJoinTask<?>[] growArray();
    // 获取方法:从栈顶(LIFO)弹出一个任务
    final ForkJoinTask<?> pop();
    // 获取方法:从栈底(FIFO)弹出一个任务
    final ForkJoinTask<?> poll();
}

WorkQueue用数组存储所有待执行的任务,线程执行时会从该队列中获取任务,如果数组为空,那么则会尝试窃取其他线程的任务。即在ForkJoinPool中存在一个由WorkQueue构成的数组成员workQueues,而在每个WorkQueue中又存在一个ForkJoinTask构成的数组成员array。

总结

本篇粗略的看了一下ForkJoinPool的源码,还有更深入的没谈到,比如invoke的externalSubmit和externalPush,还有任务执行以及线程挂起,窃取的具体实现,任务的拆分与合并实现过程等,这部分以后有时间再聊聊。