FutureTask源码分析

来源:互联网 发布:麦德斯 米科尔森 知乎 编辑:程序博客网 时间:2024/05/29 10:12

FutureTask的类图关系如下
这里写图片描述

1.Runable与Callable

java.lang.Runnable是一个接口,只有一个run()方法

public interface Runnable { public abstract void run(); }

run()方法的返回值是void,故在执行完任务后无法返回任何结果
  Callable是java.util.concurrent包下的,也是一个接口,也只有一个call()方法,类似于java.lang.Runnable的run()方法,实现Callable接口的类和实现Runnable接口的类都是可以被其它线程执行的任务

public interface Callable<V> {     V call() throws Exception; }

可以看到call()方法是有返回值的,可以将执行的结果返回

Callable和Runnable的区别:

(1)Callable中定义的是call()方法,Runnable中定义的是run()方法
(2)Callable中的call()方法可以返回执行任务后的结果,Runnable中的run()方法无法获得返回值
(3)Callable中的call()方法定义了throws Exception抛出异常,抛出的异常可以在主线程
(4)Future.get()时被主线程捕获;Runnable中的run()方法没有定义抛出异常,运行任务时发生异常时也会上抛,因为即使不加默认也会上抛RuntimeException,但异常无法被主线程获取
(5)运行Callable任务可以拿到一个Future对象代表异步运算的结果
另外,Executors类中内部实现了RunableAdapter类,通过它可以将一个Runable和一个返回值result封装为一个Callable对象。

2.Future

Future是java.util.concurrent包下的一个接口,代表着一个异步计算的结果,可以通过get()获取线程执行的返回值
Future提供了三种功能:
(1)获取任务执行的结果
(2)取消任务
(3)判断任务是否完成 或 是否取消
因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask

3.FutureTask源码分析

public class FutureTask<V> implements RunnableFuture<V>

FutureTask实现了RunnableFuture接口,那么RunnableFuture又是什么呢?

public interface RunnableFuture<V> extends Runnable, Future<V> {     /**     * Sets this Future to the result of its computation * unless it has            been cancelled.     */     void run(); }

RunnableFuture接口继承了Runnable和Future,所以它既是一个可以让线程执行的Runnable任务,又是一个可以获取Callable返回值的Future

3.1FutureTask的属性

private volatile int state;private static final int NEW          = 0;private static final int COMPLETING   = 1;private static final int NORMAL       = 2;private static final int EXCEPTIONAL  = 3;private static final int CANCELLED    = 4;private static final int INTERRUPTING = 5;private static final int INTERRUPTED  = 6;/** The underlying callable; nulled out after running */private Callable<V> callable;/** The result to return or exception to throw from get() */private Object outcome; // non-volatile, protected by state reads/writes/** The thread running the callable; CASed during run() */private volatile Thread runner;/** Treiber stack of waiting threads */private volatile WaitNode waiters;
  • callable:表示要运行的任务对象,由构造FutureTask时生成;
  • outcome: 是任务执行后的结果或异常
  • runner:执行callable的线程;
  • waiters:是一个链表结构的头部指针,每一个节点表示等待获取结果的阻塞线程,后等待线程的会排在链表头部;
  • state 是任务的运行状态
    (1)初始化时是NEW;
    (2)任务终止的状态有NORMAL(正常结束)、EXCEPTIONAL(异常结束)、CANCELLED(被取 消)、INTERRUPTED(执行中被中断),这些状态是通过set()、setException、cancel()方法触发的;
    (3)COMPLETING 和 INTERRUPTING是两个中间状态,当正常结束设置outcome属性前是COMP LETING,设置后变成NORMAL;当中断运行中线程前是INTERRUPTING,调用thread.interrupt ()后是INTERRUPTED;
    这里写图片描述

3.2构造方法

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

(1)构造方法参数是Callable定义的任务,并将state置为NEW,只有当state为NEW时,callable才能被执行;
(2)构造方法参数为Runnable和带泛型的result对象,由于Runnable本身是没有返回值的,故线程的执行结果通过result返回;可以看到通过runnable和result封装了个Callable,实际上是new RunnableAdapter(task, result),这个Adapter适配器将Runnable和result转换成Callable,并返回result;

3.3get实现

  get()方法有两个实现,一个是一直等待获取结果,直到任务执行完;一个是等待指定时间,超时后任务还未完成会上抛TimeoutException

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

内部通过awaitDone()对调用线程进行阻塞,具体实现如下:

private int awaitDone(boolean timed, long nanos)    throws InterruptedException {    //如果有超时,基于当前时间计算最终超时时间    final long deadline = timed ? System.nanoTime() + nanos : 0L;    WaitNode q = null;    boolean queued = false;    for (;;) {    //1.如果调用线程设置了中断标记,则从等待队列中移除节点并抛出异常        if (Thread.interrupted()) {            removeWaiter(q);            throw new InterruptedException();        }        int s = state;        // 2. 获取当前状态,如果状态大于COMPLETING        // 说明任务已经结束(要么正常结束,要么异常结束,要么被取消)        // 则把thread显示置空,并返回结果        if (s > COMPLETING) {            if (q != null)                q.thread = null;            return s;        }        // 3. 如果状态处于中间状态COMPLETING        // 表示任务已经结束但是任务执行线程还没来得及给outcome赋值        // 这个时候让出执行权让其他线程优先执行,等待状态发生改变后返回        else if (s == COMPLETING) // cannot time out yet            Thread.yield();        else if (q == null)           // 4. 如果等待节点为空,则构造一个等待节点            q = new WaitNode();        else if (!queued)            // 5.如果还没有入队列,则把当前节点加入waiters指向的队列的对头                        queued = UNSAFE.compareAndSwapObject(this, waitersOffset,                                                 q.next = waiters, q);        else if (timed) {            // 如果需要等待特定时间,则先计算要等待的时间            // 如果已经超时,则删除对应节点并返回对应的状态            nanos = deadline - System.nanoTime();            if (nanos <= 0L) {                removeWaiter(q);                return state;            }             // 6.阻塞等待特定时间            LockSupport.parkNanos(this, nanos);        }        else            // 7.阻塞等待直到被其他线程唤醒            LockSupport.park(this);    }}

上面这段代码有两个生僻函数的使用
(1) compareAndSwapObject
改方法解决的是并发中原子的更新引用对象
//如果offset指向的值等于expected,则原子的将Java变量更新为x
compareAndSwapObject(Object o, long offset, object expected, object x)
(2)LockSupport
LockSupport为Java并发的工具类,park表示表示阻塞当前线程,另外还有unpark表示唤醒阻塞的线程;
  awaitDone方法执行的结果就是,在任务未执行结束时,所有希望获取结果的线程都将在waiters指向的队列中进行等待;添加的顺序为,后执行get的线程被添加在队首。在threadA和threadB都阻塞等待之后的waiters结果如图。
这里写图片描述
report方法根据awaitDown返回的状态进行判断,然后返回任务执行的结果或异常。

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);}

3.4run实现

public void run() {//1    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 {            //2                result = c.call();                ran = true;            } catch (Throwable ex) {                result = null;                ran = false;            //3                setException(ex);            }        //4            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;    //5        if (s >= INTERRUPTING)            handlePossibleCancellationInterrupt(s);    }}

(1) 标记1
状态如果不是NEW,说明任务或者已经执行过,或者已经被取消,直接返回;状态如果是NEW,则尝试把当前执行线程保存在runner字段中;
(2) 标记2
执行任务,如果任务执行过程中出现了异常则执行“标记3”,顺利的话就会执行“标记4”;
(3) 标记3

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

原子的改变状态为COMPLETING,然后将异常结果复制给outcome,再将状态设置为EXCEPTIONAL;这样就完成了前面说的状态变化过程:NEW->COMPLETING->EXCEPTIONAL,并且将最终结果保存在outcome中;最后调用finishCompletion方法,唤醒所有等待的线程并返回结果outcome。
(4) 标记4
set方法和setException方法调用基本相同,唯一不同的是状态变化过程:NEW->COMPLETING->NORMAl;
(5) 标记5
如果执行任务时,cancel被调用了,那么状态将被修改为INTERRUPTING或CANCELLED,handlePossibleCancellationInterrupt方法将被执行

private void handlePossibleCancellationInterrupt(int s) {    if (s == INTERRUPTING)        while (state == INTERRUPTING)            Thread.yield(); // wait out pending interrupt}

该方法的理解需要结合cancel方法进行应用场景的举例:
当任务正在执行但并未结束,用户调用了cancel方法且参数为true,那么状态被设置INTERRUPTING
,此时任务执行完成开始调用handlePossibleCancellationInterrupt方法,因为cancel方法还未执行如下代码

if (mayInterruptIfRunning) {    try {        Thread t = runner;        if (t != null)            t.interrupt();    } finally { // final state        UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);    }}

所以会调用Thread.yield去放弃cpu的资源停止运行,直到上面的代码被执行状态变INTERRUPTED,最后任务运行结束。
finishCompletion方法如下

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

其核心作用就是换新waiters队列中所有等待的线程,唤醒的方法就是使用LockSupport.unpark,在get方法中是使用的park方法让线程休眠的。

3.5cancel实现

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;}

(1) 修改状态为NEW-> INTERRUPTING-> INTERRUPTED或NEW-> CANCELLED;
(2) 如果是调用参数为true,则会设置认为执行线程的中断标记,这个中断标记如果是单纯的从FutrueTask来看是没有任何作用的,它的作用在于callable所执行的任务是否调用了阻塞方法并且捕获了InterruptedException异常进行处理,否则调用t.interrupt并无作用;
(3) 最后调用finishCompletion线程唤醒等待队列;

4思考

Future.cancel()真的能取消任务的执行吗?

  答案是“不一定”,根据JDK中的方法注释“Attempts to cancel execution of this task”,即尝试去取消执行的任务;
如果任务正在执行,且调用cancel()时参数mayInterruptIfRunning传的是true,那么会对执行线程调用interrupt()方法。
那么问题就变成了interrupt()方法能中断线程执行吗?
  interrupt()方法不会中断正在运行的线程。这一方法实际上完成的是在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞的状态。更确切的说,如果线程被Object.wait()、Thread.join()、Thread.sleep()等阻塞,那么它将接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态。
  如果线程没有被阻塞,调用interrupt()将不起作用
  那么即使线程正在阻塞状态,并抛出了InterruptedException,线程能否真的取消执行还要看代码中是否捕获了InterruptedException和有没有做相应的对中断标示的判断逻辑