FutureTask源码分析

199 阅读10分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。


一、起源:

本文参考源码版本为 JDK1.8

1. 什么是Future?

从字面上来看,Future 表示将来的意思,也就是说现在执行某个任务可能是不能立即拿到返回值,在将来的某个时间点 当 task 执行完毕也就可以拿到了;那么这个返回值在哪呢?就封装在 future 中(实现类)

Future 最主要的作用是,比如当做一定运算的时候,运算过程可能比较耗时,有时会去查数据库,或是繁重的计算,比如压缩、加密等,在这种情况下,如果我们一直在原地等待方法返回,显然是不明智的,整体程序的运行效率会大大降低。我们可以把运算的过程放到子线程去执行,再通过 Future 去控制子线程执行的计算过程,最后获取到计算结果。这样一来就可以把整个程序的运行效率提高,是一种异步的思想

2. Future与FutureTask有何联系?

FutureTask 是 Future 的一个实现,结合 Callable 并将任务执行的返回值封装在 FutureTask 中

3. FutureTask

FutureTask 可以用做闭锁。(FutureTask 实现了 Future 语义,表示一种抽象的可生成结果的计算)。 FutureTask 表示的计算通过 Callable 来实现,相当于一种可生成结果的 Runnable,可以处于以下 3 种状态

  • 等待运行
  • 正在运行
  • 运行完成:表示计算的所有可能结束方式,包括正常结束,由于取消而结束和由于异常而结束;当进入完成状态后,它会永远停止在这个状态上

Future.get 的行为取决于任务的状态,如果任务已经完成,那么 get 会立即返回结果,否则 get 将阻塞直到任务进入完成状态,然后返回结果或者抛出异常;

FutureTask 实现

  • 在 JDK1.7 及之前,FutureTask 是基于AQS实现,状态字段 state 用来表示正在运行、已完成、已取消
  • JDK1.8, FutureTask 不在依赖于 AQS 实现,并发控制通过 CAS自旋方式更新状态字段 "state",通过一个简单的单向链表维护一个线程等待队列(想要获取返回值的线程)

FutureTask 维护的一些额外的状态变量,用来保存计算结果或者抛出的异常,此外还维护了一个引用,指向正在执行计算任务的线程(如果它当前处于运行状态),因而如果任务被取消,该线程就会中断 。

4. 闭锁

闭锁是一种同步工具类,可以延迟线程的进度直到其到达终止状态,闭锁的作用相当于一扇门

  • 当闭锁到达结束状态之前,这扇门一直是关闭的,并且没有任务线程能通过
  • 当闭锁到达结束状态时,这扇门会打开并允许所有线程通过
  • 当闭锁到达结束状态后,将不会再改变状态,因此这扇门将永远保持打开状态

Future.get 的闭锁语义:如果发生了某个事件(由 FutureTask 表示的任务执行完成或者被取消),那么线程就可以恢复执行, 否则这些线程将停留在队列中并直到该事件发生。

5. 如何使用FutureTask?

来看一个例子:

    public class OneFuture {
    
        public static void main(String[] args) {
            ExecutorService service = Executors.newFixedThreadPool(10);
            Future<Integer> future = service.submit(new CallableTask());
            try {
                System.out.println(future.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            service.shutdown();
        }
    
        static class CallableTask implements Callable<Integer> {
    
            @Override
            public Integer call() throws Exception {
                Thread.sleep(3000);
                return new Random().nextInt();
            }
        }
    }

通过 submit 提交 callable 或者 runnable 都可以得到 future,不同的是 runnable 的 future 返回值是空的

二、FutureTask原理分析

1. 从使用上看:

ThreadPoolExecutor源码分析中,未分析父类 AbstractExecutorService 相关实现(比如我们常见的 submit 方法) 这里来看看父类中 submit 方法:

    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }
    
    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }    
    
    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        return new FutureTask<T>(runnable, value);
    }

可以看到 submit 方法返回了 Future 类型的参数,Future 是一个接口,这里使用的是实现类 FutureTask,封装了如何从 future 中取值。

简单的理解就是,当某个线程执行的 task,如果已经结束并且将结果封装在了 future,那我这会直接从 future 取就可以了;如果 task 未执行完毕,此时 future 中肯定没有执行结果,但是这个时候又想要取到该结果,那就只有通过阻塞的方式来等待 task 的执行完毕。

2. 从实现上看:

通过 newTaskFor 返回的就是 FutureTask 实例,看看 FutureTask 的实现关系:

可以看到 FutureTask 最终实现了 Future 和 Runnable 接口,同时内部组合了 Callable 接口作为属性字段, 我们知道 execute 的参数是 Runnable 类型,因此可以使用 execute 来执行 futureTask 任务, 我们只需要在 run 方法中调用 callable.call 接口来执行 task 并得到结果,然后将结果封装在 future 即可。

细心的你可能已经注意到了,submit 的参数有两种类型,分别是 Runnable 和 Callable;Callable 就不说了本身就会返回结果, Runnable 是不会返回结果,FutureTask 是如何如果给属性字段 callable 赋值,又如何通过 callable.call 接口来执行 task 并得到结果呢?来看看FutureTask的一个构造方法:

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

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

可以看到最终是通过实现了 Callable 接口的适配器RunnableAdapter来封装 Runnable 的 task 和 result, 这样当调用 callable.call 接口来执行 task 并得到结果时,实际便会执行 task.run(), 并返回指定的 result (上面的例子 result 传入的是 null, 也就是没有返回值)。

来看看 FutureTask 的基本参数

    public class FutureTask<V> implements RunnableFuture<V> {
    // 状态
    private volatile int state;
    // 初始状态
    private static final int NEW          = 0;
    // 已经拿到结果,处于封装赋值futureTask阶段
    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;

    private Callable<V> callable;
    // 处理的结果
    private Object outcome; // non-volatile, protected by state reads/writes
    // 运行callable的线程,在run方法中会被初始化
    private volatile Thread runner;
    // 在future.get上等待结果的线程列表
    private volatile WaitNode waiters;

再来看看 state 的流转:

三、FutureTask源码分析

1. run

    public void run() {
        // 只有state=NEW状态才能向下执行
        // 同时将当前执行task的线程赋值给runner
        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 {
                    // 执行callable.call方法,并得到结果
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    // 设置异常状态,并改变FutureTask状态
                    setException(ex);
                }
                if (ran)
                    // 设置结果,并改变FutureTask状态
                    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);
        }
    }
    //
 

这个方法也就是重写 Runnable 接口中的 run 方法,线程执行 task 的入口方法,如果在执行 task 之前被取消或者中断了,task 将不会在执行了; 执行 task 实际就是调用 callable.call 来处理并得到结果,然后将结果封装在 futureTask 中。

1.1 setException

将异常状态封装在 futureTask 中

    protected void setException(Throwable t) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            // 将结果赋值给outcome
            outcome = t;
            // 将最终状态改为EXCEPTIONAL,也是完成状态
            UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
            // 完成后的清理操作
            finishCompletion();
        }
    }

通过原子操作先将状态改为 COMPLETING 这个临时状态(多线程情况下这里只有一个线程能操作成功,保证来了线程安全性)

1.2 set

已经得到任务的结果,将其封装在 futureTask 中

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

是不是看起来和 setException 操作一模一样,唯一的差别就是将最终状态改为了 NORMAL

2. get 阻塞等待获取值

    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        // 如果未运行或者完成中状态(已经取到结果,处于最后赋值阶段),就尝试阻塞等待
        if (s <= COMPLETING)
            // 阻塞等待
            s = awaitDone(false, 0L);
        // 根据最终的状态来判断 返回结果    
        return report(s);
    }

获取结果时会遇到这几种情况:

  • 最常见的就是当执行 get 的时候,任务已经执行完毕了,可以立刻返回,获取到任务执行的结果

  • 任务还没有结果,比如线程池任务太多,还没有轮到执行;task 执行时间太长,还没执行完

  • 任务执行过程中抛出异常,一旦这样,我们再去调用 get 的时候,就会抛出 ExecutionException 异常,不管我们执行 call 方法时里面抛出的异常类型是什么,在执行 get 方法时所获得的异常都是 ExecutionException

  • 任务被取消了,如果任务被取消,我们用 get 方法去获取结果时则会抛出 CancellationException

  • 任务超时,我们知道 get 方法有一个重载方法,那就是带延迟参数的,调用了这个带延迟参数的 get 方法之后,如果 call 方法在规定时间内正常顺利完成了任务,那么 get 会正常返回;但是如果到达了指定时间依然没有完成任务,get 方法则会抛出 TimeoutException,代表超时了

2.1 awaitDone 阻塞等待task执行完毕

    // timed表示是否有超时限制
    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);
        }
    }    

主要操作

  • 处于自循环操作中,每次循环可能都会进入不同条件中执行,直到任务完成,则将状态返回
  • 此方法会响应中断,也就是抛出 InterruptedException 异常
  • 如果第一轮循环是 NEW 状态,将会创建 WaitNode 节点;如果第二轮循环也是 NEW 状态,将会将上一轮创建的 WaitNode 节点放入线程等待列表中;

如果第三轮还是NEW状态(说明这个 task 真是固执!!! 这么几轮下来还没有执行结束),就通过LockSupport.park挂起该线程(想要获取结果的线程),这个时候就只能通过阻塞等到任务结束才能被唤醒了。

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

3. cancel

取消当前任务的执行

    // 根据参数mayInterruptIfRunning来判断是应该中断在运行中的task
    public boolean cancel(boolean mayInterruptIfRunning) {
        // 只能取消或者中断state=NEW的情况
        // 这里INTERRUPTING也是个原子性保证,在多线程环境下只有一个线程可以通过CAS操作成功,也就是只有一个线程会执行后面的逻辑
        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 {
            // remove所有waitNode节点并唤醒所有等待结果的线程
            finishCompletion();
        }
        return true;
    }

先来看看参数 mayInterruptIfRunning 的使用,如果在 task 执行之前成功的 CANCELLED 或者 INTERRUPTED,那么 task 将不会执行,从效果上来看也就没啥区别; 如果当 task 已经开始执行了,这个参数就有区别了:

  • true:cancel 成功之后,FutureTask 对应的状态是INTERRUPTED,会对任务线程进行中断(也就是会影响到任务线程的执行),同时在 get 操作时抛出 CancellationException 异常
  • false: cancel 成功之后,FutureTask 对应的状态是CANCELLED,不会对任务线程作出任何操作(也就是任务任务会执行),仅在 get 操作时抛出 CancellationException 异常

从取消流程上来看:

  • 只能取消或者中断 state = NEW 的情况
  • 根据参数 mayInterruptIfRunning 判断是否需要中断任务线程
  • 通过 finishCompletion 操作 remove 所有 waitNode 节点并唤醒所有等待结果的线程

3.1 finishCompletion

remove 所有 waitNode 节点并唤醒所有等待结果的线程

    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
    } 
    

以上流程简单来说就是情况等待队列中所有的 WaitNode,并唤醒这些等待结果的线程

4. runAndReset

此方法作用是用于多次执行 task,无需记录 task 返回的结果

    protected boolean runAndReset() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return false;
        boolean ran = false;
        int s = state;
        try {
            Callable<V> c = callable;
            if (c != null && s == NEW) {
                try {
                    c.call(); // don't set result
                    ran = true;
                } catch (Throwable ex) {
                    setException(ex);
                }
            }
        } 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
            s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
        return ran && s == NEW;
    }
    

小结

Callable 和 Runnable 的不同?

Runnable

public interface Runnable {
   public abstract void run();
}
  • 缺陷
    • 不能返回一个返回值
    • 不能抛出 checked Exception
  • Runnable 为什么要这样设计?
    • 假设 run() 方法可以返回返回值,或者可以抛出异常,也无济于事,因为我们并没有办法在外层捕获并处理,这是因为调用 run() 方法的类(比如 Thread 类和线程池)是 Java 直接提供的,而不是我们编写的。 所以就算它能有一个返回值,我们也很难把这个返回值利用到

Callable

public interface Callable<V> {
     V call() throws Exception;
}
  • 可以弥补以上 Runnable 的缺点

  • 结合 Future,将处理结果封装在 Future 中(实现类,比如FutureTask);实际上从 FutureTask 的实现上来看, 线程 Thread 执行时仍然是调用 run 进行处理,只不过在 run 方法内部我们调用了 callable.call 来处理并封装处理结果。