17-Future接口

Future接口

  • Future接口用来表示异步执行的结果,比如异步执行一个Callable任务就会返回一个Future对象,通过这个Future对象来获取异步执行的结果

  • 继承关系如下:

在这里插入图片描述

一、Future

public interface Future<V> {
    /**
     * 1.尝试取消任务,如果任务已经完成或者被取消则会取消失败,也有可能因为其他原因取消失败,方法返回true表示取消成功,
     *  false表示取消失败,通常是因为任务已经开始了
     * 2.如果任务还未开始并且被取消成功,那么这个任务再也不会被执行
     * 3.如果任务已经开始,mayInterruptIfRunning参数决定执行该任务的线程是否需要中断任务,true表示正在执行任务的线程需要中断,false表示既然已经开始执行了,就执行完毕吧
     * 4.如果方法返回true,那么isDone和isCancelled也会返回true
     */
    boolean cancel(boolean mayInterruptIfRunning);

    //如果任务在完成之前被取消,返回true
    boolean isCancelled();

    //如果任务已经完成,返回true,这里的完成不仅仅是代表正常完成,也有可能是异常终止,取消,这些都会返回true
    boolean isDone();

    //阻塞获取异步执行的结果
    V get() throws InterruptedException, ExecutionException;

    //指定超时时间的get超时
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

二、RunnableFuture

  • RunnableFuture是Future的子接口,它继承了Future并且还继承了Runnable
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里面没什么东西,关键还是要看它的直接实现类FutureTask

三、FutureTask

  • FutureTask是我们比较关心的类,它是RunnableFuture的直接实现类,由RunnableFuture接口我们知道,它具备Runnable和Future两种特性,因此FutureTask既可以作为Runnable执行,也可以作为Future获取执行结果。
  • 我们知道异步可执行对象的接口是Callable,执行一个Callable会获得一个Future结果,FutureTask虽然可以代表Future对象,但是它没有实现Callable,只是实现了Runnable,其内部会将Callable转换为Runnable来执行,我们先看构造方法

3.1 构造方法

  • FutureTask内部包装了一个Callable异步执行对象,构造的时候初始化任务对象并且设置初始任务状态
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }
  • 如果使用Runnable对象构造,那么就会包装成一个Callable对象,如下:
public FutureTask(Runnable runnable, V result) {
    //使用工具类将Runnable包装成Callable对象
    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将Runnable转换为Callable对象,类很简单,就是实现了Callable对象,
//重写call方法,调用的是Runnable对象的run方法
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.2 任务状态和属性

3.2.1 任务状态
  • FutureTask内部定义了一系列任务的运行状态用于表示任务的执行过程变化,代码中使用一个volatile的整型变量state来表示任务状态,一共有6种状态,如下:
    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;
  • 其中可能的状态变化如下:
NEW -> COMPLETING -> NORMAL
NEW -> COMPLETING -> EXCEPTIONAL
NEW -> CANCELLED
NEW -> INTERRUPTING -> INTERRUPTED
3.2.2 主要属性
//Callable任务对象
private Callable<V> callable;

//任务结果对象,不是volatile,但是通过任务的状态来保护它的可读性
private Object outcome; // non-volatile, protected by state reads/writes

//执行任务的线程对象
private volatile Thread runner;

//WaitNode对象栈,Treiber stack 是无锁栈
private volatile WaitNode waiters;  /** Treiber stack of waiting threads */

3.3 任务主体方法

3.3.1 run执行任务
  • run是执行任务的方法,继承自Runnable,里面本质是调用Callable的call,而我们前面提到过,call方法里面还是调用传入的Runnable任务对象的run方法
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
            //5.设置状态,处理中断
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }
3.3.2 set设置结果
  • run成功执行完毕之后,会调用set方法将结果保存到outcome,并且通过CAS修改任务状态
    protected void set(V v) {
        //1.CAS设置状态为COMPLETING成功,则保存结果
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = v;
            //2.设置为最终状态
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            finishCompletion();
        }
    }
3.3.3 get获取结果
  • get用于获取异步执行结果,我们看看源码细节,主要流程在awaitDone方法里面,后面解析awaitDone方法
    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        //1.获取状态,如果尚未完成,就等待直到完成
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        //2.返回结果
        return report(s);
    }
  • 再看看超时模式,超时模式也不难,就是会判断awaitDone结束之后任务的状态,如果状态还是未完成的就抛出超时异常
    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);
    }
  • report用于输出结果,如果状态是NORMAL就输出结果outcome,反之就抛出异常,这里我们可以看出,在整个过程中状态是很重要的
    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.3.4 cancel取消任务
  • 取消任务的逻辑可以看注释,比较简单,注意mayInterruptIfRunning参数,这个参数为true表示任务即使已经开始了也需要中断,false则表示如果任务已经开始就不需要中断了。
public boolean cancel(boolean mayInterruptIfRunning) {
    
    //1.根据mayInterruptIfRunning将状态从NEW修改为INTERRUPTING或者CANCELLED,修改失败就返回,
    //mayInterruptIfRunning为true则将状态修改为INTERRUPTING,反之CANCELLED
    if (!(state == NEW && UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
              mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
        return false;
    try {    // in case call to interrupt throws exception
        //mayInterruptIfRunning为true,需要中断线程
        if (mayInterruptIfRunning) {
            try {
                Thread t = runner;
                if (t != null)
                    t.interrupt();
            } finally { // final state
                UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
            }
        }
    } finally {
        //2.清理工作
        finishCompletion();
    }
    return true;
}

3.4 其他辅助方法

3.4.1 awaitDone阻塞
  • awaitDone是阻塞等待获取结果的主要逻辑,在get的时候阻塞就是调用该方法,参数代表是否是超时模式
private int awaitDone(boolean timed, long nanos) throws InterruptedException {
        //1.计算截止时间
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        WaitNode q = null;
        boolean queued = false;
        //2.自旋等待
        for (;;) {
            //3.线程被中断,抛出异常,get方法是支持中断的,并且会移除已经中断的Waiter等待节点
            if (Thread.interrupted()) {
                removeWaiter(q);
                throw new InterruptedException();
            }

            int s = state;
            //4.已经完成了,那就将线程置为null,返回线程状态
            if (s > COMPLETING) {
                if (q != null)
                    q.thread = null;
                return s;
            }
            //5.COMPLETING状态表示FutureTask执行处于中间状态,线程让出CPU给任务线程执行任务
            else if (s == COMPLETING) // cannot time out yet
                Thread.yield();
            //6.构造对象
            else if (q == null)
                q = new WaitNode();
            //7.设置waiter链表,这里的意思我猜是这样的,waiters代表了任务执行完毕之前,调用get获取结果的线程构成的一个waiterNode栈,
            //代表当前线程的节点是q,这里把q设置为当前waiter的前驱,然后将waiters变量设置为q,假设有2个线程先后来get获取结果,任务都
            //没有执行完毕,那么第二个线程对应的waiterNode就保存在waiter是变量里面,第一个线程就是第二个的后继,像一个链表连接起来
            //WaitNode4(线程4) ->  WaitNode3(线程3) -> WaitNode2(线程2) -> WaitNode1(线程1)
            else if (!queued)
                queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q);
            //8.如果是超时模式,就考虑是否超时,没有超时就使用LockSupport挂起线程
            else if (timed) {
                nanos = deadline - System.nanoTime();
                if (nanos <= 0L) {
                    removeWaiter(q);
                    return state;
                }
                LockSupport.parkNanos(this, nanos);
            }
            else
                LockSupport.park(this);
        }
    }
  • awaitDone比较不好理解的是waiters这个结构,有这个结构是因为可能有多个线程来获取结果,比如线程1、2、3按照顺序来获取结果并且都阻塞了,那么对应1、2和3会产生3个WaiterNode对象,并且3的next后继是2,2的后继是1。这些线程在自旋的时候每次都会判断state状态,如果完成就退出,而最后退出后set设置结果的时候会调用finishCompletion,finishCompletion里面会处理这些WaiterNode节点。
3.4.2 finishCompletion
  • 在取消任务、set设置完结果或者setException后会调用该方法,他会唤醒等待的线程并调用钩子方法done();
  • 如果一个任务被取消,或者已经set了结果或者异常了,那么这个任务就相当于已经完成了(正常完成或者异常完成),此时需要唤醒调用get而被阻塞的线程(发生了异常的情况),或者执行完毕清理所有的WaiterNode节点(正常情况)
private void finishCompletion() {
        // assert state > COMPLETING;
        for (WaitNode q; (q = waiters) != null;) {
            //1.将WaitNode置为null
            if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
                //2.设置成功之后自旋
                for (;;) {
                    //3.将线程对象置为null
                    Thread t = q.thread;
                    if (t != null) {
                        q.thread = null;
                        //4.唤醒线程继续执行,因为任务都被取消或者异常了,等待的线程就不能在继续傻等了,该起身走了
                        LockSupport.unpark(t);
                    }
                    //4.如果没有后继,就break
                    WaitNode next = q.next;
                    if (next == null)
                        break;
                    //5.如果有后继,就把前驱的next指针置为null(帮助gc),并处理后继节点
                    q.next = null; // unlink to help gc
                    q = next;
                }
                break;
            }
        }
        //6.调用钩子方法
        done();
        //7.成员变量置null
        callable = null;        // to reduce footprint
    }
3.4.3 removeWaiter
  • removeWaiter会尝试移除超时和已经中断的Waiter对象,代码就不解析了

3.5 内部类Waiter

  • WaitNode是等待节点。waiters成员变量保存着调用get方法获取FutureTask结果的线程构成的一个栈,其实更像一个单向链表结构,当run方法没有执行完时,调用get方法的线程会形成一个阻塞的链表,waiters代表最近的获取结果的线程,如果先后有线程1到线程4来获取结果,那么形成的结构应该是
WaitNode4(线程4) ->  WaitNode3(线程3) -> WaitNode2(线程2) -> WaitNode1(线程1)
  • Waiter代码
static final class WaitNode {
    volatile Thread thread;
    volatile WaitNode next;
    WaitNode() { thread = Thread.currentThread(); }
}

3.6 扩展方法done

3.6.1 done
  • done会在finishCompletion方法内部被调用,也就是说任务完成之后(正常或者异常,或者被取消都算完成),会唤醒等待结果而阻塞的全部线程,然后调用done来完成预定的工作,这是一个扩展接口。
protected void done() { }
3.6.2 done和CompletionService
  • 线程池框架工具CompletionService 一文中介绍了CompletionService,CompletionService能够按照任务集中执行快慢,优先获取到先执行完毕的结果,里面利用的就是FutureTask的done这个扩展点,原理很简单,就是在执行完毕之后,在done方法里面讲结果加到一个阻塞队列里面去,然后先完毕的任务结果自然在队列前面,最后完成的任务结果就在队列的尾部,由此在获取结果的时候就能够按照任务的执行速度优先获取了,CompletionService里面的任务对象是FutureTask的子类,如下:
    
    private final BlockingQueue<Future<V>> completionQueue;

    private class QueueingFuture extends FutureTask<Void> {
        QueueingFuture(RunnableFuture<V> task) {
            super(task, null);
            this.task = task;
        }
        //执行完毕之后,将Future结果对象加到阻塞队列中
        protected void done() { completionQueue.add(task); }
        private final Future<V> task;
    }
  • 更多可以阅读参考文章

四、小结

  • 我们主要分析了FutureTask这个类,它即代表可执行的任务(Runnable),也代表获取结果的Future对象,构造的时候传入的Runnable会被包装成一个Callable,执行Callable的call实际上就是调用Runnable的run
  • FutureTask内部通过集中状态来表示任务的执行过程中的变化,初始是NEW,在任务执行和结果获取的过程中都会依赖这个状态
  • FutureTask里面需要引起我们注意的是其对多线程的支持,任务执行完毕之后可能有多个线程通过Future对象来get结果,在内部是通过一个WaitNode结果来表示多线程的,它内部包装了线程对象,同时通过链表的结构组织起来。FutureTask在get获取结果的时候会阻塞,一直自旋直到完成或者被中断,或者超时,自旋过程中会不断的读取当前的state状态,state的volatile的保证线程间的可见性,这个waiterNode结构在线程任务结束之后会调用finishCompletion来清理掉,或者让阻塞的线程继续执行。

五、参考

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值