FutureTask学习

87 阅读3分钟

Future/Callable demo

Future/Callable实现了一个异步执行并且带有返回值的功能。Future代表异步执行的结果,Callable代表异步执行的任务,它会将执行结果返回给Future。demo示例:

public class FutureAndCallable {
    //定义了一个具有返回值的任务
    static class CalculationCallable implements Callable<Integer> {
        private int x;
        private int y;
        public CalculationCallable(int x, int y){
            this.x = x;
            this.y = y;
        }

        @Override
        public Integer call() throws Exception {
            System.out.println("begin call:" + new Date());
            TimeUnit.SECONDS.sleep(2);
            return x + y;
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CalculationCallable calculationCallable = new CalculationCallable(1,2);
        //用FutureTask声明一个带有返回值的任务,把CalculationCallable当做参数传进去
        FutureTask<Integer> futureTask = new FutureTask<>(calculationCallable);
        //把futureTask丢给线程去执行
        new Thread(futureTask).start();
        System.out.println("begin execute futureTask:" +new Date());
        //使用futureTask.get()获取返回值,同步阻塞方法
        Integer result = futureTask.get();
        System.out.println("result:"+result+"");
        System.out.println("end execute futureTask:" + new Date());
    }
}

Future/Callable的实现原理

上述示例中,new Thread(futureTask).start();代表启动一个线程,也就是说会执行run()方法,但是我们知道run()方法是没有返回值的,所以返回值是由其他接口提供的。

我们查看FutureTask的源码:

核心属性

//代表任务在运行过程中的状态
		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;

    /** 当前要执行的任务;返回值提供的类 */
    private Callable<V> callable;
    /** 任务的执行结果,最终通过future.get()获取的值 */
    private Object outcome; // non-volatile, protected by state reads/writes
    /** 当前执行callable任务的线程 */
    private volatile Thread runner;
    /** 单向链表,用来保存所有等待任务执行结束的线程 */
    private volatile WaitNode waiters;

FutureTask.run()方法源码

public void run() {
  //首先判断状态是否为new,并且利用自选操作把runner属性设置为当前线程。如果state!=NEW或者自旋操作失败直接返回,此时说明已经有任务执行
  if (state != NEW ||
      !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                   null, Thread.currentThread()))
    return;
  try {
    //拿到构造方法传入的Callable实现类
    Callable<V> c = callable;
    if (c != null && state == NEW) {
      V result;
      boolean ran;
      try {
        // 使用c.call()方法获取执行结果;
        result = c.call();
        ran = true;
      } catch (Throwable ex) {
        result = null;
        ran = false;
        setException(ex);
      }
      if (ran)
        //调用set()方法将执行结果保存到outcome属性中
        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)源码

//把调用call()方法获取的结果保存到outcome,并修改任务执行状态为NORMAL,
//最后调用finishCompletion()方法
protected void set(V v) {
  if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
    outcome = v;
    UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
    finishCompletion();
  }
}

 FutureTask.get()源码

public V get() throws InterruptedException, ExecutionException {
  int s = state;
  //判断当前状态如果不是COMPLETING,就调用awaitDone()方法让当钱线程等待,知道任务完成。
  if (s <= COMPLETING)
    s = awaitDone(false, 0L);
  return report(s);
}

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 (;;) {//自旋操作,目的是利用CAS机制来解决多线程竞争问题。
    //判断线程是否中断,如果中断则在队列中移除
    if (Thread.interrupted()) {
      removeWaiter(q);
      //抛出InterruptedException
      throw new InterruptedException();
    }

    int s = state;
    //判断线程是否是终止状态,直接返回任务状态。
    if (s > COMPLETING) {
      if (q != null)
        q.thread = null;
      return s;
    }
    //如果线程正在设置执行结果则调用yield()方法让出CPU资源
    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) {
      //如果设置了超时事件,则调用 LockSupport.parkNanos(this, nanos);方法阻塞线程并设置阻塞时间
      nanos = deadline - System.nanoTime();
      if (nanos <= 0L) {
        removeWaiter(q);
        return state;
      }
      LockSupport.parkNanos(this, nanos);
    }
   // 如果没有设置超时时间,则使用LockSupport.park(this)方法阻塞当前线程
    else
      LockSupport.park(this);
  }
}

上述源代码中LockSupport.park(this);阻塞了当前线程,那么此线程会在什么时候被唤醒呢?

  • 任务执行完毕,在set()方法中调用finishCompletion();方法进行唤醒
  • 线程中断,在awaitDone()方法中调用if (Thread.interrupted())进行中断判断

if (Thread.interrupted())方法

当Callable任务执行完成后,在set()方法中调用finishCompletion()方法完成阻塞线程的唤醒。

​
private void finishCompletion() {
  // assert state > COMPLETING;
  for (WaitNode q; (q = waiters) != null;) {
    //CAS操作将waiters的值设置为null。waiters表示所有阻塞线程构建的栈结构,设置为null相当于清空栈
    if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
      for (;;) {//再次从栈顶进行遍历
        Thread t = q.thread;
        if (t != null) {
          q.thread = null;
          //如果有线程不为null的节点则调用 LockSupport.unpark(t);进行唤醒
          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
}

总结:

  1. FutureTask实现了Runnable和Future接口,它表示一个带有状态及执行结果的任务,而任务执行结果的获取是基于阻塞的方式来实现的,也就是在call()方法没有返回结果之前,其他线程调用future.get()去获取结果时,FutureTask会构建一个Treiber栈结构,把当前线程存储到栈顶并通过LockSupport来阻塞,直到call()方法返回后把结果设置到outcome中,并唤醒被阻塞的线程。
  1. 在获取异步执行结果时,要么调用get()方法阻塞等待返回结果。要么通过轮询调用future.isDone()来判断任务执行状态,再调用get()方法获取结果,这会耗费CPU资源
  2. Future没有提供通知机制,我们没办法知道Future什么时候执行完成。