Java多线程-Furetue接口源代码详解

  • 2020 年 2 月 19 日
  • 筆記

一、Furetue接口源代码详解

1.1 Future的应用场景

 不管是继承thread类重写run方法还是实现runnable接口实例对象后作为参数输入至Thread类的构造器中,都无法保证获取到之前的执行结果。通过实现Callback接口,并用Future可以来接收多线程的执行结果,而这就是我在上一篇关于Callable和Runnabe接口对比中的博客中所提到的,要彻底理解它们两者之间的差别,必须拿到Future的使用中来。

 Future表示一个可能还没有完成的异步任务的结果,针对这个结果可以添加Callback以便在任务执行成功或失败后作出相应的操作。这里注意一下异步以及并行之间的区别:

  1. 并行:其在Java中指的是一般指并行计算,是说同一时刻有多条指令同时被执行,这些指令执行于同一CPU的多核上,或者多个不同CPU上的核上,或者多个物理主机甚至多个网络中。
  2. 异步:其与同步相对应,异步指的是让CPU暂时搁置当前请求的响应(CPU等待某个任务执行完毕等待期间就完全可以进行处理其他请求),处理下一个请求,当通过轮询或其他方式得到回调通知后,开始运行。多线程将异步操作放入另一线程中运行,通过轮询或回调方法得到完成通知,但是完成端口,由操作系统接管异步操作的调度,通过硬件中断,在完成时触发回调方法,此方式不需要占用额外线程。(说白了就是一个等,一个则会通知,而负责通知的对象就是Future接口的子类对象)

1.2 Future接口所提供的抽象方法

1.boolean cancel(boolean mayInterruptIfRunning);

 此方法用于尝试将此任务取消。如果当前任务还没有被执行,那么此任务就永远得不到执行。如果当前任务已经开始执行了,那么会根据参数:mayInterruptIfRunning来决定当前线程是否会中断。如果任务已经完成或者已经取消或者因为某些原因不能被取消,那么就返回flase,且取消操作失败;

2.boolean isCancelled();

 判断当前任务是否被取消了;如果成功取消,那么返回true;

3.boolean isDone();

判断当前任务是否被执行完毕,若执行完毕,则返回true;

4.V get() throws InterruptedException, ExecutionException;

 方法可以当任务结束后返回一个结果,如果调用时,工作还没有结束,则会阻塞线程,直到任务执行完毕。如果计算被取消了,那么返回异常:ancellationException,如果计算本身抛出了异常,则抛出ExecutionException,如果在当前线程等待计算完成的过程中当前线程被中断了,则抛出:InterruptedException

5.V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;

 这就是给普通的get方法设定了一个时间阈值,如果超时则不再等待当前线程。并且也多了一个当时间阈值达到的时候线程还未执行完毕会抛出异常:TimeoutException异常的机制。

1.3 Future接口下的继承关系图

RunnableFuture 这个接口同时继承Future接口和Runnable接口,在成功执行run()方法后,可以通过Future访问执行结果。这个接口都实现类是FutureTask,一个可取消的异步计算,这个类提供了Future的基本实现;

FutureTask

 能用来包装一个Callable或Runnable对象,因为它实现了Runnable接口,而且它能被传递到Executor进行执行。为了提供单例类,这个类在创建自定义的工作类时提供了protected构造函数。

SchedualFuture 这个接口表示一个延时的行为可以被取消。通常一个安排好的future是定时任务SchedualedExecutorService的结果

CompleteFuture 一个Future类是显示地完成,而且能被用作一个完成等级,通过它的完成触发支持的依赖函数和行为。当两个或多个线程要执行完成或取消操作时,只有一个能够成功。

ForkJoinTask 基于任务的抽象类,可以通过ForkJoinPool来执行。一个ForkJoinTask是类似于线程实体,但是相对于线程实体是轻量级的。大量的任务和子任务会被ForkJoinPool池中的真实线程挂起来,以某些使用限制为代价。


二、FutureTask类重要方法详解

 由关于Future接口的继承关系图我们可知此类实现了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,没有实现父接口的任何方法(这是使用继承而不是实现的原因)。而FutureTask类又是实现了RunnableFuture接口,在Java中又是允许类实现多个接口的,所以完全可以认为FutureTask实现了Runnable以及Future两个接口。下面就开始解读FutureTask的JDK源码。

**注意事项:**由于FutureTask类实现了Runnable接口,所以所有FutureTask对象总是能够被作为参数传入要求为Runnable接口的方法:比如说Thread的构造方法以及线程池对象的sunmmit()方法。

2.1 类中的属性值分析

首先我们来看此类中的一些总要状态属性值:

     * Possible state transitions:       * NEW -> COMPLETING -> NORMAL       * NEW -> COMPLETING -> EXCEPTIONAL       * NEW -> CANCELLED       * NEW -> INTERRUPTING -> INTERRUPTED       */      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;

注意事项:一个FutureTask对象只能最多包含一个任务,其内部的等待队列节点是描述等待此任务完成而线程阻塞的线程节点,而不是放置任务的,在2.2节中会详细描述。

状态名

含义

int值

state

当前任务的状态

取以下几种值大小

NEW

准备开始执行任务的状态

0

COMPLETING

正在进行任务处理中

1

NORMAL

任务执行结束,并且任务处理结果正常,没有异常出现

2

EXCEPTIONAL

执行任务的过程中出现了异常

3

CANCELLED

任务被取消了

4

INTERRUPTING

任务在执行过程中被中断,是一个中间状态

5

INTERRUPTED

中断结束

6

JDK注释给出了四种可能的任务状态:

  1. NEW(准备开始执行任务的状态)-> COMPLETING(正在进行任务处理中)-> NORMAL(任务执行结束,并且任务处理结果正常,没有异常出现)
  2. NEW -> COMPLETING -> EXCEPTIONAL(执行任务的过程中出现了异常)
  3. NEW -> CANCELLED(任务被取消了)
  4. NEW -> INTERRUPTING(任务在执行过程中被中断,是一个中间状态) -> INTERRUPTED(中断结束)

2.2 WaitNode以及等待机制案例分析

 jdk源代码:

    /**       * Simple linked list nodes to record waiting threads in a Treiber       * stack.  See other classes such as Phaser and SynchronousQueue       * for more detailed explanation.       */      static final class WaitNode {          volatile Thread thread;          volatile WaitNode next;          //注意其构造器,thread指向了调用此方法的当前线程对象          WaitNode() { thread = Thread.currentThread(); }      }

 上述代码表示的是如果有等待当前FutureTask计算完成的线程节点,这些多个节点以单向链表的形式构成。接下来就让我们使用案例说明:如果当前FutureTask对象中所含的唯一任务没有被执行完毕,而其他线程调用了FutureTask任务,那么调用的调查其就会进行等待,直至当前FutureTask对象的计算完成比如以下的案例:

import java.util.concurrent.*;    /**   * @author Fisherman   * @date 2019/9/19   */  public class TestWaittingByGetting {        public static void main(String[] args) {          int totalSum = 0;            MyCallable integerMyCallable1 = new MyCallable(1, 30, "c1");          MyCallable integerMyCallable2 = new MyCallable(31, 60, "c2");          MyCallable integerMyCallable3 = new MyCallable(61, 100, "c3");            FutureTask<Integer> integerFutureTask1 = new FutureTask<>(integerMyCallable1);          FutureTask<Integer> integerFutureTask2 = new FutureTask<>(integerMyCallable2);          FutureTask<Integer> integerFutureTask3 = new FutureTask<>(integerMyCallable3);            new Thread(integerFutureTask1).start();          new Thread(integerFutureTask2).start();          new Thread(integerFutureTask3).start();            try {              totalSum = integerFutureTask1.get() + integerFutureTask2.get() + integerFutureTask3.get();          } catch (InterruptedException e) {              e.printStackTrace();          } catch (ExecutionException e) {              e.printStackTrace();          }          System.out.println(totalSum);      }          static class MyCallable implements Callable<Integer> {          private int start;          private int end;          private String name;            public MyCallable(int start, int end, String name) {              this.start = start;              this.end = end;              this.name = name;          }            @Override          public Integer call() throws Exception {                Thread.sleep(1000);              int sum = 0;                for (int i = start; i <= end; i++) {                  sum += i;              }              System.out.println(toString() + "执行完毕,可以返回计算的值了!");              return sum;          }            @Override          public String toString() {              return name;          }      }    }

 控制台输出:(注意事项:线程执行完毕的顺序并不一定是这般)

c2执行完毕,可以返回计算的值了!  c1执行完毕,可以返回计算的值了!  c3执行完毕,可以返回计算的值了!  5050

 我们以此证明了FutureTask对象交给Thread构造器后进行执行,或者交给线程池进行执行,main线程在调用FutureTask对象.get()方法,如果计算过程未完成,会导致main线程等待此结果计算出。如果从另一个方面来阅读这个例子,可以发现此例是FutureJoinPool的基本模型,如果你把最大的任务是计算1-100的整数和,并最终将其输出,那么此任务本质上就是将0-100的任务分接为三个小任务:0-30,31-60,61-100来计算。如果我们进一步将小任务分配给多个核去执行,那么就相当于实现了FutureJoinPool的重要并行运行功能。正是因为FutureTask对象.get()方法调用在未完成计算的时候会导致当前线程等待,所以它是实现FutureJoinPool类实现的重要依据。

2.3 FutureTask类的构造器分析

 FutureTask类有两个构造器,一个参数入口是Runnable对象,另一个参数入口是Callable对象。

1.入口参数为Runnable对象的构造器

public FutureTask(Runnable runnable, V result) {          this.callable = Executors.callable(runnable, result);          this.state = NEW;       // ensure visibility of callable      }

 通过调用静态方法Executors.callable(runnable, result);将runnable对象转换为Callable对象,其中方法中result参数代表着runnbale方法将会计算出的结果值;

2.入口参数为Callable对象的构造器

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

 可见无论入口参数为何种类型,最终总是统一地将其转换或直接赋值给内部的Callable参数进行后续执行。

2.4 get()方法

jdk1.8源代码:

    /**       * @throws CancellationException {@inheritDoc}       */      public V get() throws InterruptedException, ExecutionException {//此方法会抛出执行异常          int s = state;//得到当前FutureTask对象的状态          if (s <= COMPLETING)//说明当前任务未执行完毕(可能正在执行,可能还没准备好执行)              s = awaitDone(false, 0L);//那么就调用awaitDone方法,得到更像的状态s          return report(s);//将状态s作为入口参数调用report方法      }

2.5 get(long timeout, TimeUnit unit)方法

jdk1.8源代码:

    /**       * @throws CancellationException {@inheritDoc}       */      public V get(long timeout, TimeUnit unit)          throws InterruptedException, ExecutionException, TimeoutException {          //此方法会抛出中断异常、执行异常、取消异常          if (unit == null)//如果时间为空,那么就抛出空指针异常              throw new NullPointerException();          int s = state;//得到当前FutureTask对象的状态          if (s <= COMPLETING &&//如果未完成,那么才会进行等待,即调用awaitDone方法              (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)              //如果超时了,等待返回的状态表面当前任务还是没有执行完毕,那么就抛出超时异常              throw new TimeoutException();          return report(s);//这里只有任务完成了,才会将状态值作为参数入口调用report方法,并返回      }

 可以看到get()方法和get(long timeout, TimeUnit unit)实际上内部逻辑类似,但是需要注意的一点是在Java中逻辑&的运算顺序,a&&b的逻辑判断过程中只有a为真,那么才会继续判断b是否为真,如果a==false,那么就直接返回false,就不在继续判断b是否为真了。所以要特别主要只有s <= COMPLETING成立,才会调用awaitDone(true, unit.toNanos(timeout))方法,这里隐藏的if逻辑是要特别注意

2.6 awaitDone()方法

jdk1.8源代码:

    /**       * Awaits completion or aborts on interrupt or timeout.       * 此方法会导致等待直至任务完成或者此方法在中断和时间结束和被废弃;       * @param timed true if use timed waits  timed为true意味着调用了时间阈值等待       * @param nanos time to wait, if timed //如果需要计时,那么这就是时间单位       * @return state upon completion //返回状态,直到任务完成       */      private int awaitDone(boolean timed, long nanos)          throws InterruptedException {          //如果timed为false,那么时间为0L意味着没有时间等待机制;否则deadline等于当前系统时间加上输入的等待阈值时间nanos;          final long deadline = timed ? System.nanoTime() + nanos : 0L;            WaitNode q = null;//创建一个等待节点,但是使其指向为null          boolean queued = false;          for (;;) {//进入自旋              if (Thread.interrupted()) {                  //如果当前线程中断了,那么就将当前在等待队列中的节点移出队列,并抛出中断异常                  removeWaiter(q);                  throw new InterruptedException();              }                int s = state;              if (s > COMPLETING) {//如果当前FutureTask对象的状态已经是完成状态,或是取消状态                  if (q != null) //如果当前等待节点不会空,则将等待节点中的线程指向置为null                      q.thread = null;                  return s;//结束自旋,返回FutureTask任务执行的状态              }              else if (s == COMPLETING) // cannot time out yet                  Thread.yield();//当前线程调用yield方法,在FutureTask的任务处于正在执行状态              else if (q == null)//如果等待节点为null,那么引向一个等待节点。                  q = new WaitNode();              else if (!queued)//如果当前等待节点还没有插入到队列中,那么利用CAS机制插入等待队列                  queued = UNSAFE.compareAndSwapObject(this, waitersOffset,                                                       q.next = waiters, q);              else if (timed) {//如果模式是计时的,就进行下面的执行逻辑                  nanos = deadline - System.nanoTime();//得到还有多久达到规定的截止时间                  if (nanos <= 0L) {                      //如果达到截止时间了,那么使当前线程的等待节点出列,并返回FutureTask的状态                      removeWaiter(q);                      return state;                  }                  LockSupport.parkNanos(this, nanos);//时间未到,那么有时间限制地使当前线程休眠              }              else//这里是没有时间阈值调用的awaitDone方法的执行逻辑,直接使当前线程对象没有时间限制地休眠                  LockSupport.park(this);          }      }

注意事项:

  1. 有上述代码的逻辑可知,如果当前线程中断标志位被置为true时调用了awaitDone()方法,那么会导致当前线程直接跳过等待过程,并且抛出异常,但是FutureTask对象中的任务并不会因此而取消,而是继续执行;
  2. 上述代码中当FutureTask对象的状态满足:s==COMPLETING时,说明当前FutureTask的任务处于正在执行状态,所以调用Thread.yield(),是为了尝试让当前线程让出资源CPU资源(从运行状态转为就绪状态),而不会使当前线程休眠。如果当前线程再次抢占到CPU资源,那么又会开始自旋判断。所以可以得出一个结论,如果第一次调用awaitDone方法时,FutureTask状态为0,才会进入等待队列,并使当前线程挂起,否则只会进入状态为COMPLETING的自旋;
  3. if (s > COMPLETING) {//如果当前FutureTask对象的状态已经是完成状态,或是取消状态 if (q != null) //如果当前等待节点不会空,则将等待节点中的线程指向置为null q.thread = null; return s;//结束自旋,返回FutureTask任务执行的状态 } 此处代码之所以没有使当前等待节点出列的操作,是由于如果其并没有入队,并且通过使其参数指向null操作,最终会被JVM回收;如果其已经入队了,那么则会由finishCompletion()负责移除节点,而此方法不会进行此操作,而是交给finishCompletion()方法来完成这个工作;

2.7 report(int s)方法

jdk1.8源代码:

    /**       * Returns result or throws exception for completed task.       *       * @param s completed state value       */      @SuppressWarnings("unchecked")      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);//除了以上的异常错误,那么就是判断为执行异常,并抛出;      }

2.8 finishCompletion()方法

jdk1.8源代码:

    /**       * Removes and signals all waiting threads, invokes done(), and       * nulls out callable.       */      private void finishCompletion() {          // assert state > COMPLETING;          for (WaitNode q; (q = waiters) != null;) {              if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {                  for (;;) {                      Thread t = q.thread;//得到等待队列第一个节点对象所指向的线程对象                      //如果线程对象不为空,是当前节点中的线程对象指向null,并唤醒该线程                      if (t != null) {                          q.thread = null;                          LockSupport.unpark(t);                      }                      WaitNode next = q.next;//得到下一个节点对象                      //如果下一个节点对象为null,表明已经到了队列末尾,那么跳出自旋                      if (next == null)                          break;                      q.next = null; // unlink to help gc,就是为了GC方便垃圾回收此对象q                      q = next;//q指向下一个等待节点,进行下一轮的自旋                  }                  break;              }          }            done();            callable = null;        // to reduce footprint      }

 方法finishCompletion()作用是在FutureTask任务后,将FutureTask对象中等待队列中的线程对象唤醒,并且清空等待队列中的节点。done()方法是提供给子类重写的,其本身实现为空,这样一来每次任务执行完毕都能够进行执行done()方法。所以在2.2中提供的案例完全可以改成如下形式:


三、state状态值更新的详解

 实际上在第二章中讲述了许多方法都是一个线程尝试调用FutureTask的get(),尝试获得其内部任务执行结果过程中可能涉及的相关方法,实际上还有很多其他方法可以学习,但是实现逻辑很类似,的确没有必要一一列举。但是FutureTask对象状态如何改变一定是不同的,相信读者朋友和我一样都很好奇其是如何实现的,下面我们就以正常任务的执行,不涉及取消cancel()方法的调用为例来分析此过程。

3.1FutureTask中的CAS机制学习

 首先我们需要学一下CAS机制,这里只谈用法,不谈内部cpp实现,所以并不需要额外的知识储备,但是我们得理解为何如此调用CAS方法,如此调用CAS方法的内部执行逻辑是什么:

    // Unsafe mechanics      private static final sun.misc.Unsafe UNSAFE;      private static final long stateOffset;      private static final long runnerOffset;      private static final long waitersOffset;      static {          try {              UNSAFE = sun.misc.Unsafe.getUnsafe();              Class<?> k = FutureTask.class;              stateOffset = UNSAFE.objectFieldOffset                  (k.getDeclaredField("state"));              runnerOffset = UNSAFE.objectFieldOffset                  (k.getDeclaredField("runner"));              waitersOffset = UNSAFE.objectFieldOffset                  (k.getDeclaredField("waiters"));          } catch (Exception e) {              throw new Error(e);          }      }

上面是Future’Task对象中CAS机制的初始化工作,利用Java反射机制完成了系列指向工作:

  1. stateOffset在方法compareAndSwapObject()代表对象当前的state属性
  2. runnerOffset在方法compareAndSwapObject()代表对象当前的runner属性
  3. waitersOffset在方法compareAndSwapObject()代表对象当前的的waiters属性
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

这是CAS机制的设置属性的方法之一,下面解释各个方法入口的含义:

var1 操作的对象  var2 操作的对象属性  var4 var4与var2比较,如果当前对象属性var2正好等于var4,那么才会将其更新为var5  var5 更新值

3.2 以set/setException两类方法分析FutureTask对象状态更新的原理

正常的线程任务运行过程中状态变换一定是如此顺序:

NEW -> COMPLETING -> NORMAL

 首先,New一定是发生在FutureTask对象的构造器中出现,下面是其源代码:

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

 由于同一个对象的构造器只能在一个线程中被调用,所以一定是线程安全的,所以就没有使用CAS机制来确保状态属性state的赋值。

 其次,NEW -> COMPLETING的变化一定是run方法被调用时,run方法需要第一时间进行的操作,下面为run方法的jdk1.8源代码:

public void run() {              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 {                      result = c.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);          }      }

注意事项:

  1. 作为一个FutureTask对象,有可能同一时间被多个线程调用其run方法,而为了确保只能有一个线程启动它内部的任务,其余线程都没有成功调用run方法,所以使用了以下的CAS机制,把FutureTask对象的runner更新为当前线程: if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())) return;
  2. 如果在result = c.call();方法执行过程中,那么就调用setException(ex);来进行FutureTask对象的状态值更新,否则使用set方法进行状态值的更新(利用了布尔值ran进行相关控制);
  3. finnaly{}囊括了必定会执行的代码,主要是runner=null,以防止泄露中断,以及调用 handlePossibleCancellationInterrupt(s);方法进行中断处理;

我们在分别来看set方法以及setEXception方法的jdk源代码:首先是set方法的jdk源码:

 /**       * Sets the result of this future to the given value unless       * this future has already been set or has been cancelled.       *       * <p>This method is invoked internally by the {@link #run} method       * upon successful completion of the computation.       *       * @param v the value       */      protected void set(V v) {          if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {              outcome = v;              UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state              finishCompletion();          }      }

set();方法入口参数为c.call()方法执行的结果,即任务执行的结果。

 但是一个令人深思的问题是:**为何此处线程状态的更新是线程不安全的,需要CAS机制来保证线程的安全执行?**这是由于如果当set方法是可以被其他方法调用的,不仅仅是run方法调用,比如说done方法;

执行步骤:

  1. UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING): 使当前FutureTask对象状态更新:NEW -> COMPLETING
  2. outcome = v; UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state finishCompletion(); 若1返回flase则什么都不做,否则得到run方法的指向结果赋值给对象的全局变量outcome供report方法调用,即会被get方法调用;接着更新FutureTask对象的状态值,不加检验地将状态值变为NORMAL;最后调用finishCompletion进行等待队列的唤醒以及释放节点内存资源。
    /**       * Causes this future to report an {@link ExecutionException}       * with the given throwable as its cause, unless this future has       * already been set or has been cancelled.       *       * <p>This method is invoked internally by the {@link #run} method       * upon failure of the computation.       *       * @param t the cause of failure       */      protected void setException(Throwable t) {          if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {              outcome = t;              UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state              finishCompletion();          }      }

执行步骤:

  1. UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING): 使当前FutureTask对象状态更新:NEW -> COMPLETING
  2. outcome = t; UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state finishCompletion(); 若1返回flase则什么都不做,否则得到run方法的指向结果赋值给对象的全局变量outcome供report方法调用,即会被get方法调用;接着更新FutureTask对象的状态值,不加检验地将状态值变为EXCEPTIONAL;最后调用finishCompletion进行等待队列的唤醒以及释放节点内存资源。

状态更新的小结:

  1. 非取消情况下FutureTask状态的更新的确有:NEW -> COMPLETING -> NORMAL/EXCEPTIONAL,但是可以看到,中间状态COMPLETING是抓转瞬即逝的;
  2. set以及setException方法的内部逻辑十分相似,关键是CAS操作的理解。

四、Future和FutureTask小结

相信如果这般阅读Future接口以及FutureTask类的源代码,一定对Future设计模式有所理解。下面做个小总结。

 Future接口的关键是其要求其子类实现get()方法,要求某个线程调用子类对象的get()方法时若值当前不可得,那么就阻塞那个线程,直至计算结果便可得。

 FutureTask类实现了Future接口,即实现了上述Future接口所要求的get性质,借此性质我们可以来实现大任务变小任务,最后汇总的操作(此文中中的2.2小节就举了这个例子)。按照此思路,Fork/Join框架,ForkJoinPool类的负责执行任务的最小单元就是FutureTask类对象。详细的ForkJoinPool类和Fork/Join框架请看下一章分析。