FutureTask使用及解析

时间:2024-11-09 07:21:54

        有时候我们可能会遇到这样的需要,线程执行完后需要返回运行结果,这个时候该怎么做呢?其实这时候就需要用到FutureTask了,先来看个demo:

private void testFutureTask() throws Exception {
    FutureTask<String> task = new FutureTask<>(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                ();
            }
        }
    },"gogo");
    new Thread(task).start();
    boolean done = ();
    long l1 = System.currentTimeMillis();
    String s = ();//这个方法会阻塞,直到任务完成时会返回
    boolean done1 = ();
    long l2 = System.currentTimeMillis();
    System.out.println("任务返回结果 = "+done+"   "+done1+"    time = "+(l2-l1)+"   "+s);
}

运行结果:

任务返回结果 = false   true    time = 5001   gogo

可以看到对这个Runnable封装后就可以在任务结束后获取到执行的结果。这个结果就是我们传进去的,如果你想获取里面运行的结果,那就改用构造方法中是Callable接口就可以了。

        再看构造方法之前,先来看看FutureTask的继承关系,这里是网上的一张图:


Runnable这个接口我们都知道,那剩下的就是来看看Future这个接口了:

public interface Future<V> {
    //取消任务,如果任务正在运行的,mayInterruptIfRunning为true时,表明这个任务会被打断的,并返回true;
    // 为false时,会等待这个任务执行完,返回true;若任务还没执行,取消任务后返回true,如任务执行完,返回false
    boolean cancel(boolean mayInterruptIfRunning);
    //判断任务是否被取消了,正常执行完不算被取消
    boolean isCancelled();
    //判断任务是否已经执行完成,任务取消或发生异常也算是完成,返回true
    boolean isDone();
    //获取任务返回结果,如果任务没有执行完成则等待完成将结果返回,如果获取的过程中发生异常就抛出异常,
    //比如中断就会抛出InterruptedException异常等异常
    V get() throws InterruptedException, ExecutionException;
    //在规定的时间如果没有返回结果就会抛出TimeoutException异常
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}
这个接口可以看成对任务多加一些功能,这些功能包括任务取消、任务是否取消了,任务是否执行完成了,任务执行完后返回的结果是什么。

        现在就来看看FutureTask的构造方法:

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 = (runnable, result);
    this.state = NEW;       // ensure visibility of 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;
    }
}

有两个构造方法,其中传Runnable的最后通过适配器的方式装换成了Callable,这样的话,FutureTask一定就会持有Callable对象的应用,当线程调用到run()方法时,里面调用到Callable的call() 方法就可以了。执行流程可以看成是这样的Runnable(run())--->Callable(call())。由于FutureTask实现了Future接口,这样就可以达到对run()方法实现一个监视的功能,其实就是对线程的一个监视了。线程的执行状态是通过state来保存的,那就来看看state有哪些状态:

 /** Possible state transitions:
 * NEW -> COMPLETING -> NORMAL
 * NEW -> COMPLETING -> EXCEPTIONAL
 * NEW -> CANCELLED
 * NEW -> INTERRUPTING -> INTERRUPTED
 */
private volatile int state;
//初始状态,还没有执行的,这个是在构造方法中进行赋值的
private static final int NEW          = 0;
//任务已经执行完成或者执行任务的时候发生异常,但是任务执行结果或者异常原因还没有
//保存到outcome字段(outcome字段用来保存任务执行结果,如果发生异常,则用来保存异常原因)的时候,
// 状态会从NEW变更到COMPLETING。但是这个状态会时间会比较短,属于中间状态
private static final int COMPLETING   = 1;
//任务正常执行完的最终状态
private static final int NORMAL       = 2;
//任务发生异常时的最终状态
private static final int EXCEPTIONAL  = 3;
//任务取消时的状态,任务取消当并不中断,即只调用了cancel(false)方法
private static final int CANCELLED    = 4;
//用户调用了cancel(true)方法取消任务,状态会从NEW转化为INTERRUPTING。这是一个中间状态。最终状态是INTERRUPTED
private static final int INTERRUPTING = 5;
//调用interrupt()中断任务,中断任务的最终状态
private static final int INTERRUPTED  = 6;

        现在就来看看线程开始执行的run()方法,FutureTask--->run():

public void run() {
    //如果state不是NEW状态,说明任务已经执行过或是被取消了,就直接返回
    if (state != NEW || !(this, RUNNER, null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = ();//执行任务,这里就说明真正的任务就是Callable中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);
    }
}
可以看到这里对任务主要有三种处理,正常执行完、发生异常、或是中断,这三种处理正是对应上面state的几种状态,这里我们先来看下正常执行完的情况set():

protected void set(V v) {
    if ((this, STATE, NEW, COMPLETING)) {
        outcome = v;
        (this, STATE, NORMAL); // final state
        finishCompletion();
    }
}

这里就是对任务状态的一个转换,从COMPLETING转到NORMAL,并将运行结果保存到outcome,setException()的执行逻辑和这一样,最后就是执行finishCompletion():

private void finishCompletion() {
    // assert state > COMPLETING;
    //遍历等待的线程节点,被唤醒的线程会各自从awaitDone()方法中的*()阻塞中返回
    for (WaitNode q; (q = waiters) != null;) {
        if ((this, WAITERS, q, null)) {
            for (;;) {
                Thread t = ;
                if (t != null) {
                     = null;
                    //唤醒等待的线程
                    (t);
                }
                WaitNode next = ;
                if (next == null)
                    break;
                 = null; // unlink to help gc
                q = next;
            }
            break;
        }
    }
    //模板方法,如果任务执行完后有什么需要执行的可以重写这个方法
    done();

    callable = null;        // to reduce footprint
}

你调用get()(后面会分析到)方法获取结果时,如果任务还没有执行完成时,那么就会处于等待状态,那么这个等待的线程就会加入到waiters中,如果你没有调用到get()方法,那么这个for循环就不会执行,说到这,你也许就明白了,就是唤醒在等待这个任务结果的线程并释放资源。

        既然说到获取结果的get()方法,那就来看看这个方法:

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

任务还没执行完就会进入到awaitDone()这个方法中:

private int awaitDone(boolean timed, long nanos) throws InterruptedException {
    long startTime = 0L;    // Special value 0L means not yet parked
    WaitNode q = null;
    boolean queued = false;
    for (;;) {
        int s = state;
        //任务完成、取消或是异常进入,会返回执行完后的状态
        if (s > COMPLETING) {
            if (q != null)
                 = null;
            return s;
        }
        //肯能任务线程被阻塞了,当前线程出让执行权
        else if (s == COMPLETING)
            // We may have already promised (via isDone) that we are done
            // so never return empty-handed or throw InterruptedException
            Thread.yield();
        //线程中断了,移除等待线程并抛出异常
        else if (Thread.interrupted()) {
            removeWaiter(q);
            throw new InterruptedException();
        }
        //当前等待节点为null,则初始化新节点并关联到当前线程
        else if (q == null) {
            if (timed && nanos <= 0L)
                return s;
            q = new WaitNode();
        }
        //将当前节点添加到WAITERS,等待任务完成时被唤醒
        else if (!queued)
            queued = (this, WAITERS,
                     = waiters, q);
        else if (timed) {
            final long parkNanos;
            if (startTime == 0L) { // first time
                startTime = System.nanoTime();
                if (startTime == 0L)
                    startTime = 1L;
                parkNanos = nanos;
            } else {
                long elapsed = System.nanoTime() - startTime;
                if (elapsed >= nanos) {
                    removeWaiter(q);
                    return state;
                }
                parkNanos = nanos - elapsed;
            }
            // nanoTime may be slow; recheck before parking
            if (state < COMPLETING)
                //将当前线程挂起指定时间,时间到了没有被唤醒则会抛异常
                (this, parkNanos);
        }
        else
            //挂起当前线程
            (this);
    }
}

这是一个死循环,不过这里面有一个阻塞,会将当前线程挂起,当任务完成时会被唤醒,唤醒在finishCompletion()中完成,前面有分析到,唤醒后会将执行的状态返回state。

        这样就进入到get()的report()方法中了:

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

还记得前面的set()方法么,在那里将运行结果赋值给outcome,如果是正常运行完毕的,那就将结果返回,如果不是,那就将抛出相应的异常。到这里,FutureTak就分析的差不多了,如果有不懂的地方,可自行看看源码,它的其他方法还是比较好看懂的,如果有分析不对的地方还望指正,一起学习。

总结:

        其实FutureTask还是比较简单,Callable就是他的任务,而FutureTask内部维护了一个任务状态,所有的状态都是围绕这个任务来进行的,随着任务的进行,状态也在不断的更新。任务发起者调用get()方法时,如果任务没有执行完成,会将当前线程放入阻塞队列等待,当任务执行完后,会唤醒阻塞队列中的线程。