FutureTask源码阅读

本文详细解析了FutureTask的源码,包括参数说明、任务状态转换、等待节点WaiterNode、返回结果和异常、Callable接口、任务线程runner的实现。介绍了构造函数的Callable和Runnable入参,以及run、get方法的执行流程,特别是get方法中的awaitDown过程,涉及到无锁并发栈WaiterNode的管理。同时讲解了cancel方法如何中断任务并唤醒等待线程。

1.参数说明

1.任务状态

1.新创建状态:NEW

    private static final int NEW   = 0;

2.完成中但还没完成:COMPLETING

    private static final int COMPLETING   = 1;

3.正常状态:NORMAL

    private static final int NORMAL       = 2;

4.异常状态:EXCEPTIONAL

    private static final int EXCEPTIONAL  = 3;

5.取消状态:CANCELLED

    private static final int CANCELLED    = 4;

6.中断中状态:INTERRUPTING

	private static final int INTERRUPTING = 5;

7.已经中断状态:INTERRUPTED

private static final int INTERRUPTED  = 6;

8.状态切换
参照给出的注释得到下面状态切换的几种情况:

     /**
     * Possible state transitions:
     * NEW -> COMPLETING -> NORMAL
     * NEW -> COMPLETING -> EXCEPTIONAL
     * NEW -> CANCELLED
     * NEW -> INTERRUPTING -> INTERRUPTED
     */

2.等待的节点:WaiterNode

可以看到注释这边说的是使用Treiber Stack的方式来实现,即无锁并发栈,他的基本思想也是通过CAS,后面我们会看到,这边先了解一下这个数据结构,WaitNode,记录对应的等待线程和下一个等待的节点.
在FutureTask中维护了一个WaiterNode即private volatile WaitNode waiters;
这边的waiter其实对应的就是FutureTask的get操作,因为任务可能还没处理完, get得不到结果,可能多次get,所以需要对这些get操作进行阻塞,等待真正的任务处理结束之后,再回到队列中去处理get的操作

    /**
     * Simple linked list nodes to record waiting threads in a Treiber
     * stack.  See other classes such as Phaser and SynchronousQueue
     * for more detailed explanation.
     */
    static final class WaitNode {
        volatile Thread thread;
        volatile WaitNode next;
        WaitNode() { thread = Thread.currentThread(); }
    }

3.返回结果或者返回异常:outcome

后面我们会具体说到这个,这边先了解一下,可以设置返回结果(在构造函数中设置),如果一切正常的话,按照设置的结果返回,如果发生异常则会被设置为对应的异常实例。
outcome在本地可以说算是一个缓存,当任务运行过程中,如果调用get方法,会一直等待任务结束,然后返回outcome的结果,像我们刚才说的两种情况
1.一切正常返回构造时设置的结果
2.发生异常则返回对应异常实例

    /** The result to return or exception to throw from get() */
    private Object outcome; // non-volatile, protected by state reads/writes

4.任务回调接口:Callable

	/**
	 * A task that returns a result and may throw an exception.
	 * Implementors define a single method with no arguments called
	 **/

    /** The underlying callable; nulled out after running */
    private Callable<V> callable;

5.任务线程:runner

    /** The thread running the callable; CASed during run() */
    private volatile Thread runner;

2.构造函数

1.Callable入参

通过传入Callable的实现类来构造FutureTask

    public FutureTask(Callable<V> callable) {
        if (callable == null){
            throw new NullPointerException();
        }
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

2.Runnable入参

参考注释,如果不需要指定result,可以传null。这个构造方法会把Runnable的入参包装成Callable的具体实现类,这边使用的是RunnableAdapter这个实现类

    /**
     * Creates a {@code FutureTask} that will, upon running, execute the
     * given {@code Runnable}, and arrange that {@code get} will return the
     * given result on successful completion.
     *
     * @param runnable the runnable task
     * @param result the result to return on successful completion. If
     * you don't need a particular result, consider using
     * constructions of the form:
     * {@code Future<?> f = new FutureTask<Void>(runnable, null)}
     * @throws NullPointerException if the runnable is null
     */
    public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }
    public static <T> Callable<T> callable(Runnable task, T result) {
        if (task == null)
            throw new NullPointerException();
        return new RunnableAdapter<T>(task, result);
    }

RunnableAdapter支持传入result,就是我们上面说的outCome怎么返回的问题,这边不赘述

    static final class RunnableAdapter<T> implements Callable<T> {
        final Runnable task;
        final T result;
        RunnableAdapter(Runnable task, T result) {
            this.task = task;
            this.result = result;
        }
        public T call() {
            task.run();
            return result;
        }
    }

3.方法实现

1.线程执行:run

2.获取运行结果:get

如果当前的FutureTask是NEW或者COMPLETING状态,也就是还没有到最终状态的时候,需要把get操作等待在wait节点的链表中。
当前任务结束之后,返回结果,即report,report可能返回对应的result,也可能是抛出错误。

    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        //线程还在NEW或者COMPLETING状态则进行等待.
        if (s <= COMPLETING) {
            s = awaitDone(false, 0L);
        }
        //等待结束之后,返回结果
        return report(s);

    }

1.返回执行结果:report

    private V report(int s) throws ExecutionException {
        Object x = outcome;
        if (s == NORMAL) {
            //正常状态,返回之前设置的Result
            return (V) x;
        }
        //如果是被中断或者取消,抛出错误
        if (s >= CANCELLED) {
            throw new CancellationException();
        }
        throw new ExecutionException((Throwable) x);
    }

2.等待任务执行结束:awaitDown

要看一下awaitDown方法如果等待线程执行完任务。
这边的判断条件比较多(好几个else if…这代码写的不是很优雅啊)。我们先一步一步来看一下他的实现
1.刚开始先是根据timed是否计时来获取最终结束时间(如果有计时,没有计时的时候为0)
2.然后进入死循环
        1.如果当前线程是中断状态,则把waiter移除队列,等下看一下这个移除操作,不仅仅是移除自己这个节点,移出之后抛出中断异常
        下面这些判断都是在同一个ifelse的判断中所以是有关系的,这边把这些判断按照条件成立前后关系标注A1~n来阐述能更好说明问题.
        A1.如果状态已经不是NEW和COMPLETING了,可以返回结果了,这边有一步是q.thread=null,这个操作是告诉后面的removeWaiter方法,这个waiter是可以移除的节点了。
        A2.如果线程还是COMPLETING,则调用Thread.yield,进入就绪态,让出CPU重新抢占
        A3.如果FutureTask是NEW的状态要新创建一个wait节点
        A4.FutureTask还是NEW或者COMPLETING的状态并且waiter还没有进队列,因为不知道要等待多久,所以还是把waiter插入队列
        A5.此时状态还是NEW或者COMPLETING,如果有计时在实现计时的处理,时间到了移除队列,时间没有到则挂起对应时间等待。
        A6.最后是没有计时的情况,休眠等待被唤醒

  private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
        //通过timed来判断是否计时和结束时间
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        WaitNode q = null;
        boolean queued = false;
        //进入死循环
        for (; ; ) {
            //线程被中断则要把当前的Wait节点移除掉
            if (Thread.interrupted()) {
                removeWaiter(q);
                throw new InterruptedException();
            }

            int s = state;
            //线程已经是COMPLETING之后的状态:正常、异常、中断
            if (s > COMPLETING) {
                //判断节点不为空,则把节点的线程置空,返回状态
                if (q != null) {
                    //并发各种情况都有可能,所以这边也要清空一下Thread
                    q.thread = null;
                }
                return s;
            }// cannot time out yet
            //如果线程还是COMPLETING,则调用Thread.yield,进入就绪态,让出CPU重新抢占
            else if (s == COMPLETING) {
                Thread.yield();
            }
            //这边应该是  !s > COMPLETING && q!=null,就是说FutureTask还是NEW、或者COMPLETING的状态,谁也不知道他什么时候执行,我先进入wait queue等待一下。
            else if (q == null) {
                q = new WaitNode();
            }
            //这边应该是  !s >= COMPLETING && q!=null,就是说FutureTask还是NEW的状态,谁也不直到他什么时候执行,我先进入wait queue等待一下。
            else if (!queued) {
                //这边的处理是  q.next = waiters.也就是说后面进来的节点会成为FutureTask的waiter,通过这个waiter去查找后面的元素,是栈的实现
                queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q);
            }
            //这边就是 !s >= COMPLETING && q!=null && 加入队列,如果有计时,开始挂起计时
            else if (timed) {
                //时间到了则从节点中移除
                nanos = deadline - System.nanoTime();
                if (nanos <= 0L) {
                    removeWaiter(q);
                    return state;
                }
                //否则挂起对应时间
                LockSupport.parkNanos(this, nanos);
            } else {
                //不满足上述情况进行挂起,当FutureTask的run运行结束之后判断waiter的Thread不为空则会进行唤醒
                LockSupport.park(this);
            }
        }
    }

3.移除waiter:removeWaiter

我们看到注释上面其实也有提到,这边是通过cas的方式来实现的无锁操作,因为这边会存在两个循环,在某些情况下可能从头开始处理,就是说可能你已经处理到第十个节点了,但是发现有问题就要从头再来,所以这边告诉你可能会有效率问题,因为处理的waiter节点是对应的get操作,人家也说了设计的目的考虑的是waiter是非常少量的,如果你蛇精病死循环去get,你还能怪人家?
这边方法一进来,先是把当前需要取消的节点的thread设置为null,我们在上面其实也有看到这样的操作,其实对于FutureTask来说,当你的waiter的thread为空的时候就需要被移除了,算是一个标记。
然后我们进入了第一个死循环,而第一个死循环结束的条件是第二个死循环不满足q != null这个条件才会结束。
我们来看一下第二个循环,这边存在三个判断:
1.先判断waiter.thread是否为空,如果不为空就要往下一个节点去查找了
2.不满足上述的判断即 当前节点的p.thread == null并且前一个节点不为空(这说明不是头结点),会先把当前节点p断开,然后再判断前一个节点pre的thread是不是为空,这个pre的thread按道理如果是第一次检查过了不会为空,但是你永远想象不到并发的情况下可能它对应的waiter也要取消,把它的thread置空了,那么这样就会存在一种极端情况就是执行到这一步的时候,pre这个节点被另一个removeWaiter的线程抛弃了,那么他的next就是null了,按道理这个时候应该直接break出去,因为该做的做完了,但是这个removeWaiter真是尽职尽责,他会跳会第一个循环重新再查找一边,保证所有的该清除的节点都清除掉
3.第三步则是比较特殊的情况就是满足q.thread == null && pre == null,即刚好要移除的就是第一个waiter,但是移除之后依然会回到一个死循环继续查找
直到最后,找到最后一个waiter(直到next == null)才退出死循环。

    /**
     * Tries to unlink a timed-out or interrupted wait node to avoid
     * accumulating garbage.  Internal nodes are simply unspliced
     * without CAS since it is harmless if they are traversed anyway
     * by releasers.  To avoid effects of unsplicing from already
     * removed nodes, the list is retraversed in case of an apparent
     * race.  This is slow when there are a lot of nodes, but we don't
     * expect lists to be long enough to outweigh higher-overhead
     * schemes.
     */
    private void removeWaiter(WaitNode node) {
        if (node != null) {
            //清空Thread,把要移除的wait节点做一个标记
            node.thread = null;
            // restart on removeWaiter race
            retry:
            for (; ; ) {
                //这边的结束条件是 q == null;
                for (WaitNode pred = null, q = waiters, s; q != null; q = s) {
                    s = q.next;
                    //如果我们要移除的节点不是头结点,因为我们已经设置了Thread = null了.继续往下一个节点查找
                    if (q.thread != null) {
                        pred = q;
                    }
                    //这个地方显然是不满足第一个if才会进来,即 q.thread == null && pred != null
                    else if (pred != null) {
                        //把q节点断开了
                        pred.next = s;
                        // check for race
                        //这边再来一个判断是因为在多线程的情况下,可能在上面pred的thread也被另一个线程调用removeWaiter置为空了。
                        //所以这边返回头结点,继续从头开始查找处理
                        if (pred.thread == null) {
                            continue retry;
                        }
                    }
                    //不满足上面条件,即 q.thread == null && pre == null.就是说当前需要移出的元素在栈顶,然后把栈顶元素替换掉.
                    else if (!UNSAFE.compareAndSwapObject(this, waitersOffset, q, s)) {
                        continue retry;
                    }
                }
                break;
            }
        }
    }

3.取消执行:cancel

cancel方法这边有一个入参是mayInterruptIfRunning,当这个入参为true的时候表示是中断取消的,如果是false则是其他原因。
这边如果不满足条件不会进行取消,这个条件是:任务的状态是NEW,如果是NEW,此时会通过CAS设置他的状态,设置成功才能进入取消,也可能在设置的过程中任务被启动了,就会失败,失败则直接返回
如果设置成功,这边只会对中断产生的cancel进行中断处理,否则直接进入finally中进行处理。finishCompletion方法表示唤醒所有等待的节点,因为任务已经被取消或者已经处理结束了(就是变成某些终止状态了,必须进行结束)。

    //入参表示是中断还是取消节点,true表示中断,false表示取消
    public boolean cancel(boolean mayInterruptIfRunning) {
        //如果不满足当前条件的不能取消: 不满足 新创建的线程并且设置成对应取消状态,直接返回,也就是说以一旦进入运行是不能取消的,除非还在等待
        if (!(state == NEW && UNSAFE.compareAndSwapInt(this, stateOffset, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED))){
            return false;
        }
        // in case call to interrupt throws exception
        try {
            //如果是CANCELED只是在上面设置状态.然后在finally中进行处理
            //如果是中断会在这边设置中断状态
            if (mayInterruptIfRunning) {
                try {
                    Thread t = runner;
                    if (t != null){
                        t.interrupt();
                    }
                } finally { // final state
                    UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
                }
            }
        } finally {
            //结束处理,唤醒所有等待的wait节点
            finishCompletion();
        }
        return true;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

了-凡

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值