深入理解 异步任务执行类FutureTask源码分析

 

一.FutureTask这个类

首先,很多人会发出这样的疑问,为什么会出现这个类?在java的发展历程中,不断的更新迭代,只为一个目的,解决问题!什么问题需要解决呢?异步计算,对,FutureTask的出现就是为了解决这个问题。光从字面上看,并没有体现出异步,确实也是如此,在使用该类的时候,要特别小心,要实现异步必须将FutureTask交给一个线程或线程池去执行。

1.类的继承实现关系

FutureTask实现了RunnableFuture接口,而RunnableFuture接口又继承了Runnable接口和Future接口。这里有个疑问一直没弄明白,既然RunnableFuture中,并没有新的抽象方法,只有一个继承至Runnable的run方法,为什么FutureTask不直接实现Runnable和Futrue两个接口,非要搞个RunnableFuture出来,有知道的请在下方评论区留言告知,感激不尽!

2.设计思想

Doug Lea在设计很多并发工具的时候都采用了相似的思想,即AQS的设计思想。大致分为控制生命周期的状态State;存放等待线程的队列;贯穿整个state的修改和线程的出队入队的CAS,当然在用CAS的时候常常伴随着自旋和volatile。抓住了这些点,我们采用在阅读源码的时候以上帝的视角去审视每句代码的意图。

3.状态state

state总共有7个值NEW= 0、COMPLETING= 1、NORMAL= 2、EXCEPTIONAL= 3、CANCELLED= 4、INTERRUPTING =5、INTERRUPTED= 6。下面以图的形式展示显得直观些,并且还体现了状态的只被允许的转换路径

4.队列

该队列是线程安全的单链,节点为WaitNode,该类只有thread和next两个属性,thread用于存放等待结果的线程,在调用get方法时设置为当前线程。其实这个队列是一个stack,叫做Treiber stack,几十年前就提出来了。好早啊!没什么特别的,就是通过CAS来保证出栈和入栈线程安全的这么个stack。

5.属性

属性五个,执行状态state,任务对象callable,返回值outcome,执行任务的线程runner,等待结果的线程waiters。

6.构造方法

FutureTask(Callable<V> callable)和FutureTask(Runnable runnable, V result),构造器本身没什么说的,值得一说的是这里用到了一种设计模式。如何将在java1.0就已经设计好的runnable可以像用callable一样,作为参数传给futuretask,将两者合二为一呢?下面粘贴出FutureTask(Runnable runnable, V result)的源码以及相关的源码:

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);
    }
//runnable--->RunnableAdapter<T>---->Callable<T>
//给runnable披上callable的外衣
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;
        }
    }

通过类名RunnableAdapter我们可以得知,答案就是适配器模式。但是我个人感觉,可能仔细的人也发现,这种实现很生硬,为什么要将传入的result作为返回值又返回呢?是不是很鸡肋。

7.主要方法

要想理解FutureTask是如何实现异步执行任务的关键,在于理解这几个方法:get()run()cancel()。从字面上大致理解一下,run()是执行任务的,肯定会调用callable的call方法,任务执行完设置返回值。get()异步获取执行结果,因为执行的run方法需要一定时间,所以该方法会阻塞线程,那么就会涉及到线程入队和出队的问题。cancel()取消方法的执行,叫取消不要准确,应该称为打断任务,打断了任务,等待结果的线程是不是也该做出反应。

二、源码分析

1.run()

前面已经大概介绍了run()方法,进入方法,首先是检查状态和设置runner,并且还保证了执行任务只有一个线程。然后就是拿到callable对象,调用call方法,如果执行正常结束,调用set(result)方法设置返回值,如果call方法内部抛出异常,调用setException()抛出异常。

public void run() {
        //这一步,过滤作用和设置runner。过滤是指state不为NEW直接return,且
        //一旦有线程已经进入run方法,其他调用run方法的线程进入将直接return。
        //确保了只有一个线程执行run方法。
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            //再读取一遍state,因为可能被cancel
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

那么set(result)到底做了什么事?将state由NEW改为COMPLETING,这里说明一件事,COMPLETING并不是表示任务正在执行,而是表示任务已经执行完成,正在设置返回值。然后设置返回值,将state置为NORMAL最终态。这里有点不解,为什么不是CAS操作?Doug Lea给出的解释是:Transitions from these intermediate to final states use cheaper ordered/lazy writes because values are unique and cannot be further modified.大概意思是“因为值是唯一的且不会再被进一步修改,所以从中间态转变成最终态使用的是更低开销的顺序或者延迟写入。”回到代码中,接下来是执行finishCompletion(),介绍完setException()再一起说。

protected void set(V v) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = v;
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            finishCompletion();
        }
    }

如果在执行call方法时出现异常,便会调用setException()方法,与set方法类似,只不过最终态不同,返回值是一个异常对象。

protected void setException(Throwable t) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = t;
            UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
            finishCompletion();
        }
    }

上面两个方法都调用了finishCompletion()方法,试想下,换成你来设计,你会在任务返回值都已经设置好了,接下来干什么?

没错,清空等待任务结果的线程队列,并唤醒他们。这里为什么有个for循环?目的是为了确保waiters不被并发修改。如果if判断waiters变了,就再执行循环一次,直到将waiters成功置为null为止。然后死循环里就是遍历waiters,将每个线程唤醒,并置为null,便于GC。done()是一个空的protected方法,子类可以实现该方法,用于在任务执行结束做出的动作。最后任务对象置为null。其实了解完FutureTask你会发现,该方法只有三个地方会用到,分别是set、setexception、cancel,也就是说不管task最后是以何种方式结束都是要唤醒每个线程的。

private void finishCompletion() {
        // assert state > COMPLETING;
        for (WaitNode q; (q = waiters) != null;) {
            if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
                for (;;) {
                    Thread t = q.thread;
                    if (t != null) {
                        q.thread = null;
                        LockSupport.unpark(t);
                    }
                    WaitNode next = q.next;
                    if (next == null)
                        break;
                    q.next = null; // unlink to help gc
                    q = next;
                }
                break;
            }
        }

        done();

        callable = null;        // to reduce footprint
    }

截止,run方法其实还没结束,回看run方法,finally中的代码还没执行。置空runner。检查状态,s >= INTERRUPTING其实就是 INTERRUPTING和INTERRUPTED。目的是检查是否被Cancel(true),如果没有执行完成操作,等待其执行完。

public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }
private void handlePossibleCancellationInterrupt(int s) {
        // It is possible for our interrupter to stall before getting a
        // chance to interrupt us.  Let's spin-wait patiently.
        if (s == INTERRUPTING)
            while (state == INTERRUPTING)
                Thread.yield(); // wait out pending interrupt

       

        // We want to clear any interrupt we may have received from
        // cancel(true).  However, it is permissible to use interrupts
        // as an independent mechanism for a task to communicate with
        // its caller, and there is no way to clear only the
        // cancellation interrupt.
        
    }

至此,run方法就真的结束了。以下是run方法大致流程图:

2.get()方法

如果s <= COMPLETING说明返回值还没设置好,此时调用get()的线程进入等待队列。如果条件不满足,执行report(s),返回相应的值。

public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }

我们先来看report(s)方法,三种可能,正常返回、被取消、执行call时异常。

 private V report(int s) throws ExecutionException {
        Object x = outcome;
        if (s == NORMAL)//正常返回
            return (V)x;
        if (s >= CANCELLED)//被取消,抛出取消异常
            throw new CancellationException();
        throw new ExecutionException((Throwable)x);//执行时抛出异常,抛出执行异常
    }

再来分析awaitDone方法,进入就是死循环,循环体先是判断当前线程即调用get的线程是否被打断,是,如果还没入队就直接抛出打断异常,如果已经入队,从队列中移出。往下,s > COMPLETING表示已经设置完返回值或者被取消,将thread置空,并返回。s == COMPLETING在set和setException方法被调用,表示正在 设置返回值,当前线程让出执行权。q为null表示还没生成节点,那么新建一个节点。!queued表示queued为false时,还没入队,将其入队,CAS操作。接下来是调用get(time)的时候,如果等待时间到了,就从队列中移出,没有就等待parknanos(nanos)。否则就park阻塞。线程被finishCompletion方法唤醒后继续执行下一次循环。

private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        WaitNode q = null;
        boolean queued = false;
        for (;;) {
            if (Thread.interrupted()) {
                removeWaiter(q);
                throw new InterruptedException();
            }

            int s = state;
            if (s > COMPLETING) {
                if (q != null)
                    q.thread = null;
                return s;
            }
            else if (s == COMPLETING) // cannot time out yet
                Thread.yield();
            else if (q == null)
                q = new WaitNode();
            else if (!queued)
                queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                     q.next = waiters, q);
            else if (timed) {
                nanos = deadline - System.nanoTime();
                if (nanos <= 0L) {
                    removeWaiter(q);
                    return state;
                }
                LockSupport.parkNanos(this, nanos);
            }
            else
                LockSupport.park(this);
        }
    }

以下是get方法的一个流程图,相信这样更直观一点:

3、cancel()

从方法第一个条件可以看出,state为NEW才会执行下面的代码。不等于NEW,if判断中&&后面的CAS不会执行。如果等于NEW不管mayInterruptIfRunning是true还是false都会执行。简单来说,state不为NEW直接执行return false;state为NEW,

  • mayInterruptIfRunning为true,State置为INTERRUPTING。
  • mayInterruptIfRunning为false,state置为CANCELLED。

往下,判断mayInterruptIfRunning,为true,即state为INTERRUPTING,打断执行run的线程,并将state置为INTERRUPTED。

最后调用finishCompletion,置空队列,唤醒等待线程,置空callable。

public boolean cancel(boolean mayInterruptIfRunning) {
        if (!(state == NEW &&
              UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
                  mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
            return false;
        try {    // in case call to interrupt throws exception
            if (mayInterruptIfRunning) {
                try {
                    Thread t = runner;
                    if (t != null)
                        t.interrupt();
                } finally { // final state
                    UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
                }
            }
        } finally {
            finishCompletion();
        }
        return true;
    }

以下就是cancel方法的大致流程图:

4.其他方法

FutureTask实现了runnable和Future接口,除了上面的方法外还有其他方法。

get()的超时版本:

    public V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
        if (unit == null)
            throw new NullPointerException();
        int s = state;
        if (s <= COMPLETING &&
            (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
            throw new TimeoutException();
        return report(s);
    }

获取状态的方法:

    public boolean isCancelled() {
        return state >= CANCELLED;
    }

    public boolean isDone() {
        return state != NEW;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值