【JUC源码】FutureTask详解

简介

本文主要分析FutureTask的实现原理,以运行过程中state的变化为切入点,最终把整个过程归纳为三张流程图。

FutureTask的整体结构

private volatile int state; // 标记当前运行状态

private Callable<V> callable; // 实际运行的代码

private Object outcome;  // callable运行结果

private volatile Thread runner; // 运行callable的线程

private volatile WaitNode waiters; // 等待callable运行完成的线程

FutureTask实现了获取运行结果的功能,那实现这个功能需要什么呢?

  1. 一个保存结果的变量,这是很容易想到的。这就是上面的outcome。
  2. 如果获取结果前,执行代码的线程还没跑完,那么获取结果的这个线程肯定得阻塞,所以就要一个阻塞队列。这就是上面的waiters。

FutureTask是可以打断任务的,那就得知道执行当前任务的线程,所以也需要保存runner。

callable很好理解,最后来看一下state。

private static final int NEW          = 0; // 初始状态/正在运行
private static final int COMPLETING   = 1; // 已经完成callable调用/执行callable中抛异常
private static final int NORMAL       = 2; // 正常完成
private static final int EXCEPTIONAL  = 3; // 运行出现异常

private static final int CANCELLED    = 4; // 打断,但不会停止正在运行的任务
private static final int INTERRUPTING = 5; // 打断,会调用Thread#interrupt,停止正在运行的任务
private static final int INTERRUPTED  = 6; // 打断完成

先看一下,后面再详细解释。
只可能有如下四种状态转移,刚开始可以先不要看COMPLETING和INTERRUPTING,这两个状态不是很好理解,它们主要起到并发时的保护作用。

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

上述四种转移路径分别对应如下四种情况:

  1. 正常执行callable完毕,最后就是NORMAL。
  2. 执行callable中的代码时出了异常,最后就是EXCEPTIONAL。
  3. 调用了cancel,并且设置mayInterruptIfRunning为false,最后就是CANCELLED。
  4. 调用了cancel,并且设置mayInterruptIfRunning为true,最后就是INTERRUPTED。

现在对state的转移路径有了初步的认识,有点懵逼也没事,接下去分析run,get,cancel三个方法,就能把这些状态串起来了。也能明白INTERRUPTING和COMPLETING的作用了。

run

在这里插入图片描述

接下去看一下源码:

public void run() {
	  // 如果state不为NEW,或者cas赋值runner失败。那就不要去执行callable里的代码了。
	  // state不为NEW说明之前有其他线程调用过cancel,或者当前任务已经执行过了。cas失败说明有其他线程已经在执行当前任务了。
      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里的代码
                   result = c.call();
                   ran = true;
               } catch (Throwable ex) {
                   result = null;
                   ran = false;
                   // 异常处理
                   setException(ex);
               }
               if (ran)
               // 正常结束处理
                   set(result);
           }
       } finally {
           
           runner = null;
           
           int s = state;
           // 如果被cancel的可运行时打断需要的处理
           if (s >= INTERRUPTING)
               handlePossibleCancellationInterrupt(s);
       }
   }

接下去看一下setException和set

protected void setException(Throwable t) {
	// cas修改state为COMPLETING
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
    	//结果赋值给outcome
        outcome = t;
        // 修改state为EXCEPTIONAL
        UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
        // 唤醒waiters中等待的线程。
        finishCompletion();
    }
}
protected void set(V v) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = v;
         // 修改state为NORMAL
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
        finishCompletion();
    }
}

可以看到state状态COMPLETING和NORMAL(EXCEPTIONAL)是连续修改的,中间只有一个outcome,那为什么要多设置一个COMPLETING?具体的原因等看完get和cancel了再分析。

private void handlePossibleCancellationInterrupt(int s) {
	// 等待state变为INTERRUPTED
    if (s == INTERRUPTING)
        while (state == INTERRUPTING)
            Thread.yield(); // wait out pending interrupt
}

如果state是INTERRUPTING,handlePossibleCancellationInterrupt就自旋,等待state变为INTERRUPTED,具体原因后面分析。

get

在这里插入图片描述

 public V get() throws InterruptedException, ExecutionException {
     int s = state;
     if (s <= COMPLETING)
         s = awaitDone(false, 0L);
     return report(s);
 }
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);
}

代码很简单,就自己看看吧。

cancel

在这里插入图片描述
上图中的流程是在当前state为NEW的时候才执行的,因为当前state不为NEW说明callable已经执行完了,那打断就没有意义了。
上下两条分支主要区别就是有没有调用Thread#interrupt,也就是能不能实现运行中打断。
接下去看看源码

public boolean cancel(boolean mayInterruptIfRunning) {
	// 设置INTERRUPTING或者CANCELLED
   if (!(state == NEW &&
          UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
              mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
        return false;
    try {    
        if (mayInterruptIfRunning) {
        	// 如果允许运行中打断
            try {
                Thread t = runner;
                if (t != null)
                    t.interrupt();
            } finally { // final state
            	// 设置INTERRUPTED
                UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
            }
        }
    } finally {
    	// 不管允不允许运行中打断,都唤醒等待的线程。
        finishCompletion();
    }
    return true;
}

代码很短。
接下去补充一下之前提到的几个问题。

补充

  1. INTERRUPTING的作用
    现在假设没有INTERRUPTING,那允许运行时打断的流程就是先调用Thread#interrupt,再把state设为INTERRUPTED,当然此时就要用cas进行赋值了。
    考虑如下的场景:A线程(负责打断)Thread#interrupt调用完后,B线程(负责执行callable)因为被打断,会抛出异常(之前在sleep),或者继续执行(之前在park),那就会进入setException或者set,然后把state用cas改为COMPLETING,最后改为EXCEPTIONAL或者NORMAL,然后时间片再分给A线程,A线程再尝试去用cas把state改成INTERRUPTED就会失败了。所以本来state的最终状态应该是INTERRUPTED,最终输出CancellationException,但现在就是正常输出或者抛ExecutionException。

  2. COMPLETING的作用
    setException和set中能不能直接把state改为EXCEPTIONAL或者NORMAL?如下代码:

    protected void set(V v) {
         if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, NORMAL)) {
             outcome = v;
             finishCompletion();
         }
     }
    protected void setException(Throwable t) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, EXCEPTIONAL)) {
            outcome = t;   
            finishCompletion();
        }
    }
    

    这样很显然是不行的,因为get的时候会先判断state是否<=COMPLETING,如果>COMPLETING就会直接输出了,但如果按上面的写法,很可能outcome此时还是空的。

    在上面的代码中,如果把outcome赋值写到if的上面可以吗?
    这样有个小问题就是即使其它线程调用了cancel,还是会执行赋值操作。不过好像不影响输出。但整体代码就比较乱,作者本来用COMPLETING统一了没被打断时callable刚执行完的状态,就非常清晰。

  3. handlePossibleCancellationInterrupt
    这是在run最后执行的方法,作用就是如果当前state为INTERRUPTING,就自旋直到state变为INTERRUPTED。什么意思呢?INTERRUPTING代表cancel刚开始,还没有调用Thread#interrupt,INTERRUPTED代表Thread#interrupt已经调用过了。如果Thread(也就是runner)就执行一个FutureTask好像看不到作用,不自旋直接退出对结果也没什么影响,但是如果runner接下去还要执行其他的任务,那就很恐怖了,cancel中还有一个Thread#interrupt随时可能调用,那就会影响到其它的任务。
    从这里可以看出INTERRUPTING的另一个作用:如果没有INTERRUPTING,那根本没法知道有没有其它线程在打断当前任务,有可能这个任务执行完了,runner都开始执行其他任务了,然后打断上一个任务的线程执行了Thread#interrupt,这就很麻烦了。

感叹一下Doug Lea是真牛逼啊。。。虽然整体流程比较简单,但是细节很多很多,水平有限,哪里没理解对,写的不好的地方请大佬指正。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值