Java FutureTask源码总结 从数据结构层面理解FutureTask

FutureTask类源码重点

  1. FutureTask是一种可取消的异步任务,实现了RunnableFuture接口也就相当于实现了Runnable接口,并重写run()方法,所以其任务是在线程中执行的,可以和线程池联用,RunnableFuture源码可以看我这篇文章 RunnableFuture
  2. FutureTask实现RunnableFuture接口也就相当于实现了Future接口,自然就具有其接口的含义,Future源码可以看我这篇文章 Future
  3. FutureTask的操作大致分为执行任务、获取任务执行结果和取消任务三个操作
  4. FutureTask内部有一个等待链表,其节点为WaitNode,节点内部只有一个线程,用于获取任务执行结果时,将想要获取结果的线程放入节点并阻塞,而且放在WaitNode构成的链表中存储,因为当get获取结果时,该任务可能没有执行完成

FutureTask类的实现

FutureTask类属性

  1. state是FutureTask的状态,它的值有NEW(新建)COMPLETING(完成)NORMAL(正常)EXCEPTIONAL(异常)CANCELLED(取消)INTERRUPTING(中断中)INTERRUPTED(已中断),state表示着FutureTask的任务执行的状态
  2. callable是FutureTask具体执行的任务,Callable接口是函数式接口只定义了一个带返回值的call()方法,是Runnable的改进,Callable源码可以看我这篇文章 Callable
  3. outcome是一个Object,用来存放任务执行完的结果或执行过程发生异常产生的异常对象Throwable
  4. runner 是执行该FutureTask的任务的线程对象
  5. waiters是等待线程的链表头节点,
  6. STATE、RUNNER、WAITERS是变量句柄,变量句柄的作用是代替Atomic类对变量指向CAS或原子操作,为什么JDK9要涌入变量句柄呢,因为以前如果我们想使用CAS或者原子操作,只能将我们变量定义为AtomicInteger、AtomicLong这些类,现在引入变量句柄我们仍然可以像平常一样定义我们的变量,但是还是可以进行CAS和原子操作
public class FutureTask<V> implements RunnableFuture<V> {
	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;
	private Object outcome;
	private volatile Thread runner;
	private volatile WaitNode waiters;

	private static final VarHandle STATE;
    private static final VarHandle RUNNER;
    private static final VarHandle WAITERS;
}

等待线程的链表节点

可以看到每个等待节点内部都有一个线程

static final class WaitNode {
    volatile Thread thread;
    volatile WaitNode next;
    WaitNode() { thread = Thread.currentThread(); }
}

执行任务

run操作

  1. 可以看到只有当state状态为NEW,且用变量句柄RUNNER对变量runner执行CAS操作将执行任务的线程runner设为当前线程成功,才会执行以后的操作,不满足这两个条件中的一个直接返回,这样可以防止多个线程重复执行同一任务
  2. 执行任务就是取出Callable执行其call()方法,获取执行结果,如果没有发生异常则调用set方法设置执行结果,如果发生了异常则调用setException方法设置返回结果为异常对象
  3. 最终的finally块要将runner即执行任务的线程设为空,而且判断当前状态state是不是中断中和已中断状态,如果是则调用handlePossibleCancellationInterrupt,让出CPU等待任务被中断
  4. 最后,FutureTask实现了Runnable,所以这个run方法实际是在线程中执行的
public void run() {
	// 防止多个线程执行同一任务
    if (state != NEW ||
        !RUNNER.compareAndSet(this, 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 = null;
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}
set操作
  1. 使用CAS尝试把state设为COMPLETING完成状态,如果成功,则将上面call的执行结果也就是传入进来的v赋给outcome,outcome是用来容纳执行结果或执行中的异常对象的
  2. setRelease会把state的值设为NORMAL正常状态,setRelease能确保在此次访问state变量之后和之前的读和写不会重排序,读是从主存上读取变量,写是把写缓冲区的数据刷盘至主存
  3. 最后调用finishCompletion方法将waiters即等待节点链表头节点置空,且把等待链表上所有节点的线程置空,并唤醒该阻塞线程
protected void set(V v) {
    if (STATE.compareAndSet(this, NEW, COMPLETING)) {
        outcome = v;
        STATE.setRelease(this, NORMAL);
        finishCompletion();
    }
}
finishCompletion操作
  1. 如果waiters等待节点不为空则进入循环,先使用CAS将waiters设为空,如果成功则进行后续操作,不成功则在外层for循环自旋直至操作成功
  2. CAS成功后,则把q的内部线程置为空,并调用LockSupport.unpark接触该线程的阻塞,一直循环把q链表后面移动,对每个等待节点都进行释放操作并唤醒其内部线程,然后才跳出循环,第一个break跳出内存循环,第二break虽然在if语句里面但是仍然可以跳出第一层循环
  3. 最后执行done()操作,done操作是要子类重写的方法,默认为空,用来执行类似任务完成后一个回调方法的操作,再将callable即该FutureTask的任务对象置空
  4. 为什么要有这些等待节点呢,因为当runner线程执行任务过程,还会有其他线程调用get()方法想获取任务执行结果,此时其他线程每个都会创建一个WaitNode,放入waiters链表中阻塞等待,直到runner执行完任务后调用finishCompletion把它们全部唤醒,去获取结果
private void finishCompletion() {
    for (WaitNode q; (q = waiters) != null;) {
        if (WAITERS.weakCompareAndSet(this, 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; 
                q = next;
            }
            break;
        }
    }
    done();
    callable = null; 
}
setException操作

可以看到和上面的set方法几乎一模一样,只是最后用setRelease将state设为EXCEPTIONAL异常状态

protected void setException(Throwable t) {
    if (STATE.compareAndSet(this, NEW, COMPLETING)) {
        outcome = t;
        STATE.setRelease(this, EXCEPTIONAL); 
        finishCompletion();
    }
}
handlePossibleCancellationInterrupt操作

Thread.yield() 方法,使当前线程由执行状态,变成为就绪状态,让出cpu时间,等待其他线程的中断

 private void handlePossibleCancellationInterrupt(int s) {
    if (s == INTERRUPTING)
        while (state == INTERRUPTING)
            Thread.yield();
}

runAndReset操作

可以看到和run操作几乎一模一样,就是没有执行set方法,set方法作用如下

  1. 将Callable的call方法返回的返回值赋值给outcome
  2. 把state设为COMPLETING再设为NORMAL
  3. 将waiters置空,并遍历等待链表将所有节点的线程置空,并解锁阻塞线程

由于没有执行set操作改变state状态,所以runAndReset()方法是可以多次执行的,runAndReset()设计用于本质上执行多次的任务

protected boolean runAndReset() {
    if (state != NEW ||
        !RUNNER.compareAndSet(this, null, Thread.currentThread()))
        return false;
    boolean ran = false;
    int s = state;
    try {
        Callable<V> c = callable;
        if (c != null && s == NEW) {
            try {
                c.call(); 
                ran = true;
            } catch (Throwable ex) {
                setException(ex);
            }
        }
    } finally {
        runner = null;
        s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
    return ran && s == NEW;
}

获取任务执行的结果

get操作

  1. 获取当前任务执行状态state,如果state为NEW或COMPLETING,则执行awaitDone方法阻塞等待任务执行完成,COMPLETING不算正常完成任务最终状态,NORMAL才是最终状态
  2. 然后调用report返回已完成任务的结果或引发异常
public V get() throws InterruptedException, ExecutionException {
    int s = state;
    if (s <= COMPLETING)
        s = awaitDone(false, 0L);
    return report(s);
}
awaitDone操作
  1. startTime是任务开始时间,q是一个等待节点包含当前线程,queued是q节点是否在等待节点链表上面排队,queued为true,说明q节点在等待链表上排队
  2. for循环自旋,首先获取state状态,如果状态大于COMPLETING,则可能是NORMAL(正常完成)、EXCEPTIONAL(异常)、CANCELLED(取消)、INTERRUPTING(中断中)、INTERRUPTED(已中断)这些状态,这些状态都应当直接结束等待,将p的线程置为空,返回当前的state
  3. 如果当前状态等于COMPLETING说明已经在NORMAL的中间状态了,任务差一点就做完了,则调用Thread.yield()向调度程序发出的提示,表示当前线程愿意使用处理器,加快任务执行(调度程序可以随意忽略此提示)
  4. 如果使用Thread.interrupted方法检测到当前线程被打上中断标志,则从等待链表中移除等待节点p,并抛出InterruptedException中断异常以中断程序
  5. 如果q等于null,先判断函数是否设置了超时返回以及是否超时,再创建以当前线程为内部遍历的WaitNode节点p
  6. 如果q节点没有在waiters等待链表中排队,则使用CAS设置waiters为q,在这之前把q.next指向waiters,相当于在队头插入q节点,再使用CAS把队头设位置q
  7. 如果函数设置了超时返回,则进入判断超时返回的区域,第一次进入时startTime为0,则把startTime设为系统当前的纳秒时间, parkNanos阻塞时长设为函数传参进来的nanos,然后直接判断state是否小于COMPLETING,也就是处于new状态时,用 LockSupport.parkNanos(this, parkNanos)方法阻塞当前线程parkNanos纳秒
  8. 第二次及以后进入这个超时判断块时,则计算当前纳秒时间和开始纳秒时间的差值,如果大于给定的超时时间nanos则直接先移除等待链表中的q节点,再返回状态,如果没有大于则计算剩余要阻塞的时间nanos - elapsed,继续调用LockSupport.parkNanos阻塞当前线程
  9. 上面有两个地方要注意的,为什么parkNanos定义成final还能在两处地方更改,因为这是一个for循环,而且那两处地方是if和else结果,for循环每次重新进入时会重新定义一次,而if和else每次都只会执行其中一个
  10. 另一个要注意的地方是 LockSupport.parkNanos阻塞当前线程那任务怎么执行呢,这就知道调用get()方法的线程和执行任务的线程是不一样的,执行任务的线程是runner,而调用get方法的线程是其他线程,在等runner执行完任务之前会在等待链表中阻塞,直到runner执行完任务后调用finishCompletion把它们全部唤醒,去获取结果
  11. 最后一个else,如果前面都不满足,即不满足状态大于等于COMPLETING,不满足当前线程被打上中断标志,不满足还没排队,不满足设置超时时间,即当任务状态为NEW且线程没有被打上中断标志,且在等待链表中排队,没有设置超时的线程会被调用 LockSupport.park一直阻塞,直到runner执行完任务后调用finishCompletion把它们全部唤醒,去获取结果
private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
        long startTime = 0L;
        WaitNode q = null;
        boolean queued = false;
        for (;;) {
            int s = state;
            if (s > COMPLETING) {
                if (q != null)
                    q.thread = null;
                return s;
            }
            else if (s == COMPLETING)
                Thread.yield();
            else if (Thread.interrupted()) {
                removeWaiter(q);
                throw new InterruptedException();
            }
            else if (q == null) {
                if (timed && nanos <= 0L)
                    return s;
                q = new WaitNode();
            }
            else if (!queued)
                queued = WAITERS.weakCompareAndSet(this, q.next = waiters, q);
            else if (timed) {
                final long parkNanos;
                if (startTime == 0L) { 
                    startTime = System.nanoTime();
                    if (startTime == 0L)
                        startTime = 1L;
                    parkNanos = nanos;
                } 
                else {
                    long elapsed = System.nanoTime() - startTime;
                    if (elapsed >= nanos) {
                        removeWaiter(q);
                        return state;
                    }
                    parkNanos = nanos - elapsed;
                }
                if (state < COMPLETING)
                    LockSupport.parkNanos(this, parkNanos);
            }
            else
                LockSupport.park(this);
        }
    }
report操作
  1. s是上面传入的任务执行状态,如果任务执行状态为NORMAL正常,则返回执行结果 outcome
  2. 如果 s 大于等于 CANCELLED,说明s >= 4,则s可能是CANCELLED、INTERRUPTING、INTERRUPTED ,这三个状态,则抛出CancellationException异常
  3. 如果 s 为 EXCEPTIONAL,也就是 s 等于 3,则outcome就是一个Throwable对象,将其传入ExecutionException中,并抛出ExecutionException异常
 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);
 }

get(long timeout, TimeUnit unit)操作

可以看出和get差别不大,只是判断当awaitDone方法返回时,如果此时状态仍然小于等于COMPLETING,是任务没完成的状态,则抛出超时异常

 public V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
    if (unit == null)
        throw new NullPointerException();
    int s = state;
    if (s <= COMPLETING &&
        (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
        throw new TimeoutException();
    return report(s);
}

取消任务

cancel操作

  1. 第一个if语句有点复杂,我们可以先不看外部的非号!,里面的意思是状态为NEW 且 使用CAS设置state的值成功,而里面的三元运算符只是决定把state设成什么状态而已,加了个非号就是如果状态不为NEW或使用CAS将state设置为其他状态不成功,直接返回false,就是取消失败的意思
  2. 只有什么情况才会继续执行下面代码进行取消呢,只有当state为NEW且使用CAS将该state设为INTERRUPTING 或CANCELLED这两个状态成功时,才执行后续操作,这是为了防止多线程并发取消,因为只有一个线程能够CAS成功,则该线程可以执行后续操作
  3. mayInterruptIfRunning是中断的意思,如果设为true,则使用CAS将state设为 INTERRUPTING 中断中状态
  4. try处如果 mayInterruptIfRunning为true,则调用该线程的t.interrupt方法,打上中断标志,并调用setRelease将state设为INTERRUPTED已中断状态
  5. 不管是直接取消,还是中断,都会执行finishCompletion,清空等待链表,唤醒所有等待获取结果的线程
public boolean cancel(boolean mayInterruptIfRunning) {
   if (!(state == NEW && STATE.compareAndSet
          (this, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
        return false;
    try {    
        if (mayInterruptIfRunning) {
            try {
                Thread t = runner;
                if (t != null)
                    t.interrupt();
            } finally {
                STATE.setRelease(this, INTERRUPTED);
            }
        }
    } finally {
        finishCompletion();
    }
    return true;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lolxxs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值