线程池原理(二):可执行任务及其返回值

线程池将任务提交和任务执行分离,线程只是一个工作者,它可以执行有返回值的任务,也可以执行没有返回值的任务。接下将详细讨论这些线程执行的任务。

先看下执行任务基本框架:

 

图1 任务框架

1. Runnable

Runnable是一个没有返回值的任务,看下该接口的定义:

public interface Runnable {
    //线程执行该任务时将会运行该方法
    public abstract void run();
}

 这个接口很简单,下面代码片断是一个简单的用法:

Thread worker = new Thread(new Runnable() {
  @Override
  public void run() {
    System.out.println("I'm running");
  }
});
worker.start();

 创建一个线程并为该线程指定一个任务,启动线程后该任务就会被执行。

2. Callable

Callable是一个有返回值的任务,看下该接口的定义:

public interface Callable<V> {
    //线程执行任务时将会运行该方法
    V call() throws Exception;
}

 

Callable的call方法可以抛出异常(包括运行时和检查异常),而Runnable的run方法不会抛出检查异常。

另外,注意到Callable是一个泛型接口,call方法的返回值是一个泛型。

3. Future

Future接口代表一个异步执行结果,它提供了检查任务是否完成的方法。在取任务执行结果时,如果任务未完成,取结果的线程会阻塞,直到任务完成并返回。

Future提供了取消任务的方法,如果执行没有返回值(例如Runnable)的任务,并且希望能够取消任务,可以定义Future

  1. public interface Future<V> {
  2.     //尝试取消正在执行的任务,如果任务已经完成该操作就会失败。如果任务还未执行取消就会成功。
  3.     //如果任务正在执行,方法的参数就会指示线程是否需要中断,从而尝试停止任务的执行。
  4.     boolean cancel(boolean mayInterruptIfRunning);
  5.     //任务完成之前是否已经被取消
  6.     boolean isCancelled();
  7.     //任务是否已经完成
  8.     //任务正常终止、抛出异常、或者被取消,该方法都会返回true
  9.     boolean isDone();
  10.     //取任务执行的结果,如果任务未完成,取结果的线程会阻塞。
  11.     //如果任务被取消了,该方法抛出CancellationException
  12.     //如果线程被中断,抛出中断异常
  13.     V get() throws InterruptedException, ExecutionException;
  14.     //支持超时时间的get方法
  15.     //可能抛出超时异常
  16.     V get(long timeout, TimeUnit unit)
  17.         throws InterruptedException, ExecutionException, TimeoutException;
  18. }

 

4. RunnableFuture

RunnableFuture继承了Runnable和Future接口。可以这样理解,RunnableFuture是一个具有异步执行结果的任务,并且可以通过Future的方法取该任务的执行结果。看下RunnableFuture接口的定义:

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

 

5. FutureTask

FutureTask是一个可取消的异步任务,实现了RunnableFuture接口。

FutureTask所代表的任务执行有7种状态,分别由7个静态私有变量表示,我们先看下这7个状态:

  1.      //任务执行的最新状态,在下面7个状态中取值
  2.     private volatile int state;
  3.     //新建状态,任务都从该状态开始
  4.     private static final int NEW          = 0;
  5.     //正在执行任务的状态
  6.     private static final int COMPLETING   = 1;
  7.     //任务正常执行完成的状态
  8.     private static final int NORMAL       = 2;
  9.     //任务执行过程中抛出了异常
  10.     private static final int EXCEPTIONAL  = 3;
  11.     //任务被取消(不响应中断)
  12.     private static final int CANCELLED    = 4;
  13.     //任务正在被中断
  14.     private static final int INTERRUPTING = 5;
  15.     //任务已经中断
  16.     private static final int INTERRUPTED  = 6;

任务一共有7种状态,但是同一个任务只会经历4种状态转移序列

    NEW -> COMPLETING -> NORMAL
    NEW -> COMPLETING -> EXCEPTIONAL
    NEW -> CANCELLED
    NEW -> INTERRUPTING -> INTERRUPTED

任务总会从NEW状态开始,一个状态转移序列可以为:

    从任务新建状态、到任务正在执行状态、到任务正常结束的状态,这是一个任务正常结束的状态转移序列。
    从任务新建状态、到任务正在执行状态、到任务执行过程中抛出异常转到了异常状态。FutureTask的任务执行是会捕获异常的(运行时和检查异常都会捕获),捕获到异常后将任务状态标识为异常状态。
    从任务新建状态到任务被取消状态,只有在线程不可中断的时候调用cancel方法,任务才可能经历该状态转移序列。
    从任务新建状态、到任务正在被中断状态、到任务已经中断状态,只有在线程可中断的时候调用cancel方法,任务才可能经历该状态转移序列。

FutureTask可以保证已经完成的任务不会被再次运行或者被取消。

再看下FutureTask其他的属性:
    //需要执行的任务,任务执行结束后将会被设置为null
    private Callable<V> callable;
    //调用get方法取任务执行的返回结果,get方法也有可能抛出异常
    private Object outcome;
    //执行该任务的线程,任务执行完成之后设置为null
    private volatile Thread runner;
    //通过链表保存等待获取结果的线程
    private volatile WaitNode waiters;

下面我们详细介绍FutureTask的方法,创建一个FutureTask后,通常会提交给一个线程池运行,线程将会执行FutureTask的run方法,先看下run方法:

  1. public void run() {
  2.     //1. 如果该任务不是新建状态,说明该任务已经被调度执行过了,直接返回,否则
  3.     //2. 如果已经有线程将要执行该任务,直接返回,否则
  4.     //3. 设置执行该任务的线程为当前线程
  5.     if (state != NEW ||
  6.         !UNSAFE.compareAndSwapObject(this, runnerOffset,
  7.                                      null, Thread.currentThread()))
  8.       return;
  9.     try {
  10.       Callable<V> c = callable;
  11.       //只有任务(Callable)不为空并且任务状态为NEW,才会去执行该任务
  12.       if (c != null && state == NEW) {
  13.         V result;
  14.         boolean ran;
  15.         try {
  16.           //执行任务
  17.           result = c.call();
  18.           //到这,说明任务已经执行完成了
  19.           ran = true;
  20.         } catch (Throwable ex) {
  21.           //捕获任务执行过程中抛出的异常,设置任务状态为EXCEPTIONAL并将任务返回结果outcome设置为异常ex
  22.           result = null;
  23.           ran = false;
  24.           setException(ex);
  25.         }
  26.         //如果任务正常完成,设置任务状态为NORMAL并将任务返回结果outcome设置为result
  27.         if (ran)
  28.           set(result);
  29.       }
  30.     } finally {
  31.       //不管任务执行结果如何,最终都将执行线程runner设置为null,防止该任务再次被执行
  32.       runner = null;
  33.       //再读一次,避免丢失中断,为什么呢?
  34.       //如果runner线程已经结束,但是其它线程调用了runner的interrupted方法,这时runner是感知不到其他
  35.       //线程中断过自己,因为自己已经结束了。如果runner不等待一会,就可能会丢失中断消息
  36.       int s = state;
  37.       if (s >= INTERRUPTING)
  38.         //处理可能的中断(cancel(true)),如果当前任务正在中断过程中,runner自旋等待一会,直到中断完成。
  39.         //注意,这里的中断是由别的线程中断当前runner的,如果当前runner发现其他线程正在中断自己,
  40.         //该runner就会让出CPU时间,回到线程可执行态,直到中断完成
  41.         handlePossibleCancellationInterrupt(s);
  42.     }
  43. }

任务执行过程中如果抛出了异常,调用setException设置任务的返回值为该异常:

  1. protected void setException(Throwable t) {
  2.     //原子的将任务状态从NEW转移到COMPLETING
  3.     //这种状态转移序列对应了状态变化:NEW -> COMPLETING -> EXCEPTIONAL
  4.     if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
  5.       //设置任务执行结果为异常t
  6.       outcome = t;
  7.       //任务最终状态为EXCEPTIONAL
  8.       UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL);
  9.       finishCompletion();
  10.     }
  11. }

任务正常执行结束后,调用set方法设置任务的返回值:

  1. protected void set(V v) {
  2.     //这种状态转移序列对应了状态变化:NEW -> COMPLETING -> NORMAL
  3.     if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
  4.       outcome = v;
  5.       //任务最终状态为NORMAL
  6.       UNSAFE.putOrderedInt(this, stateOffset, NORMAL);
  7.       finishCompletion();
  8.     }
  9. }

 

FutureTask任务提交给线程池执行后,我们通过get方法获取该任务的执行结果,看下get方法的实现:

  1. public V get() throws InterruptedException, ExecutionException {
  2.     int s = state;
  3.     //状态为NEW或者COMPLETING,说明任务没有完成,等待任务执行完成
  4.     if (s <= COMPLETING)
  5.       s = awaitDone(false, 0L);
  6.     //返回任务执行结果,可能会抛出异常
  7.     return report(s);
  8. }

如果当前任务状态处于NEW或者COMPLETING的状态,需要等待任务执行完成。

学习awaitDone方法之前 ,我们先看下FutureTask的内部类WaitNode:

  1. static final class WaitNode {
  2.     //保存等待获取结果的线程
  3.     volatile Thread thread;
  4.     //下一个等待节点
  5.     volatile WaitNode next;
  6.     WaitNode() { thread = Thread.currentThread(); }
  7. }

FutureTask的私有变量waiters就是WaitNode类型的,waiters用于保存等待获取任务执行结果的线程,如果有多个线程都调用get方法并且任务并没有执行完成,就需要将这些线程保存起来,等任务执行完成后唤醒这些线程。WaitNode的实现其实就是一个简单的单向链表。

看下awaitDone方法:

  1. //timed:是否超时等待
  2. //nanos:timed=true时有效,等待的时间,单位是纳秒
  3. private int awaitDone(boolean timed, long nanos)
  4.   throws InterruptedException {
  5.     //到期时间
  6.     final long deadline = timed ? System.nanoTime() + nanos : 0L;
  7.     WaitNode q = null;
  8.     boolean queued = false;
  9.     for (;;) {
  10.       //如果当前线程被中断,从等待链表中移除该节点,并抛出异常
  11.       if (Thread.interrupted()) {
  12.         removeWaiter(q);
  13.         throw new InterruptedException();
  14.       }
  15.       int s = state;
  16.       if (s > COMPLETING) {
  17.         if (q != null)
  18.           q.thread = null;
  19.         //如果任务已经执行完成,返回任务执行状态
  20.         return s;
  21.       }
  22.       //任务正在执行,让出CPU时间
  23.       else if (s == COMPLETING)
  24.         Thread.yield();
  25.       else if (q == null)
  26.         //当前线程创建一个等待节点
  27.         q = new WaitNode();
  28.       else if (!queued)
  29.         //如果节点未入队,将节点入队并将新节点设置为链表的第一个节点
  30.         queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
  31.                                              q.next = waiters, q);
  32.       else if (timed) {
  33.         //超时等待
  34.         nanos = deadline - System.nanoTime();
  35.         if (nanos <= 0L) {
  36.           removeWaiter(q);
  37.           return state;
  38.         }
  39.         LockSupport.parkNanos(this, nanos);
  40.       }
  41.       else
  42.         //挂起当前线程
  43.         LockSupport.park(this);
  44.     }
  45. }

awaitDown方法的逻辑就是将当前取结果的线程放到WaitNode的等待队列中,当任务有结果了,awaitDown也就会返回,否则就将取结果的线程挂起。

awaitDown方法返回后,get方法调用report方法,返回任务执行状态,看下report方法的实现:

  1. private V report(int s) throws ExecutionException {
  2.     Object x = outcome;
  3.     if (s == NORMAL)
  4.       return (V)x;
  5.     if (s >= CANCELLED)
  6.       throw new CancellationException();
  7.     throw new ExecutionException((Throwable)x);
  8. }

report方法返回任务正常处理后的返回值,也可能抛出异常。

    当任务处理状态为NORMAL时,说明任务正常处理完成,返回outcome。
    当任务处理状态为CANCELLED、INTERRUPTING或者INTERRUPTED时,说明任务被取消了,抛出CancellationException。
    当任务处理状态为EXCEPTIONAL时,说明任务执行过程中抛出异常了,此时outcome应是异常的信息,转换成Throwable后继续抛出。FutureTask的任务在线程池中被执行的时候,如果抛出了异常,get结果的时候会得到该异常。

FutureTask任务是可取消的,这也是它一个重要特性,看下cancel方法:
//参数mayInterruptIfRunning标识任务执行过程中是否可中断

  1. public boolean cancel(boolean mayInterruptIfRunning) {
  2.     //只有任务状态为NEW才可被取消,否则说明已经完成、取消或者中断了
  3.     if (state != NEW)
  4.       return false;
  5.     if (mayInterruptIfRunning) {
  6.       //可以被中断,将任务状态设置为INTERRUPTING
  7.       if (!UNSAFE.compareAndSwapInt(this, stateOffset, NEW, INTERRUPTING))
  8.         return false;
  9.       Thread t = runner;
  10.       if (t != null)
  11.         //中断正在执行该任务的线程
  12.         t.interrupt();
  13.       //设置任务最终状态为INTERRUPTED
  14.       //此任务状态转移序列对应了:NEW -> INTERRUPTING -> INTERRUPTED
  15.       UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
  16.     }
  17.     //任务不可被中断,将任务的状态设置为CANCELLED
  18.     //此任务状态转移序列对应了:NEW -> CANCELLED
  19.     else if (!UNSAFE.compareAndSwapInt(this, stateOffset, NEW, CANCELLED))
  20.       return false;
  21.     finishCompletion();
  22.     return true;
  23. }
  24.  

cancel方法尝试取消任务的执行,如果参数为false,任务将会经历NEW -> CANCELLED状态序列。如果参数为true,任务将会经历NEW -> INTERRUPTING -> INTERRUPTED状态序列。

如果一个任务正在run,此时执行cancel方法,如果该任务不响应中断,那么不会影响任务的执行,只会影响任务的状态。如果任务执行时发现状态不是NEW,可能是被cancel了,任务就不会被执行。

我们发现,有很多方法最后都调用了finishCompletion,顾名思义,该方法是任务结束后的收尾方法,看下该方法的实现:
//唤醒等待队列中的线程并从等待队列移除

  1. private void finishCompletion() {
  2.     //自旋,直到waiters为空
  3.     for (WaitNode q; (q = waiters) != null;) {
  4.       //尝试将waiters设置为null
  5.       if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
  6.         //唤醒等待队列中所有的节点线程
  7.         for (;;) {
  8.           Thread t = q.thread;
  9.           if (t != null) {
  10.             q.thread = null;
  11.             //唤醒该节点线程
  12.             LockSupport.unpark(t);
  13.           }
  14.           WaitNode next = q.next;
  15.           if (next == null)
  16.             break;
  17.           q.next = null;
  18.           q = next;
  19.         }
  20.         break;
  21.       }
  22.     }
  23.     //FutureTask中这是一个空方法,留给子类去实现
  24.     done();
  25.     //任务设置为null
  26.     callable = null;
  27. }

 

finishCompletion方法唤醒等待队列waiters中所有节点线程并删除节点。

最后看下FutureTask两个构造函数:

  1. //一个参数的构造函数,指定了callable
  2. public FutureTask(Callable<V> callable) {
  3.     if (callable == null)
  4.       throw new NullPointerException();
  5.     this.callable = callable;
  6.     //任务的初始状态都是NEW
  7.     this.state = NEW;
  8. }

//两个参数的构造函数,指定了callable,并且callable的返回值指定为result

  1. public FutureTask(Runnable runnable, V result) {
  2.     //Executors.callable方法只是包装了一个callable,指定了callable的返回值为result
  3.     this.callable = Executors.callable(runnable, result);
  4.     this.state = NEW;
  5. }

好了,FutureTask介绍到此为止。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值