多线程——FutureTask源码分析

前言

FutureTask是多线程编程中经常用到的一个类,在线程池中,它常常以 executor.submit() 方法调用中的形式隐式存在。它返回了一个Future对象,从而实现了多线程编程中获取异步结果的一种途径。
FutureTask是一个封装了任务的类,可以理解成是Runnable/Callable的包装类。原生的Runnable/Callable都是同步执行,FutureTask像是一个单独的容器,通过持有任务的成员变量,并获取执行该任务的线程,让任务在这FutureTask中运行,然后将运行结果存放在容器中。调用者可以在不同的阶段获取这个结果。

帮忙办件事

故事: 同学A接到了快递电话,要求马上到校门口取快递,否则要等到下午再过来。正要出门赶往校门口取快递,肚子疼了起来。于是同学A交给了同学B一个装有监控的盒子,让同学B去校门口将东西装在盒子里拿回来,自己则跑去厕所蹲起了茅坑,玩起了手机,并监控起了盒子情况。
这是一个很常见的异步执行过程,普通的Runnable和Callable也能实现。但是与它们不同的是,FutureTask会返回一个Future对象,就像同学A的监控器,可以随时知道快递的当前情况。而Runnable和Callable则没有。看下例:

以Runnable为例:

Runnable r = () -> System.out.println("取快递");
new Thread(r).start();

FutureTask:

 FutureTask t = new FutureTask(() -> System.out.println("取快递"), true); 
 new Thread(t).start();

都是异步编程,FutureTask有什么过人之处呢?
在Runnable例子中,引用 r 无法获取任务当前执行结果。但是在FutureTask中,引用 t 可以获取任务当前执行结果。
它是怎么做到的呢?

FutureTask

看一下继承结构,FutureTask实现了RunnableFuture,RunnableFuture又继承了Runnable和Future接口。所以FutureTask既是一个Runnable,也是一个Future。
在这里插入图片描述
它的核心设计思想是,将真正的Runnable/Callable和执行它的Thread封装到FutureTask里面,并将它们的执行结果储存起来。

成员变量

根据这个思路看一下它的成员变量:

private Callable<V> callable;    // 真正的任务

private Object outcome; // 任务执行的最终结果

private volatile Thread runner; // 执行任务的线程

private volatile WaitNode waiters; // 简单的线程链表

在FutureTask的设计思想中,执行结果是当任务完成时返回,所以持有的成员变量是Callable,因为Runnable执行完成后不会返回结果。而Runnable在FutureTask中会被转换为Callable。

回到故事中,当同学A看盒子时,可能会存在几种结果?

  1. 同学B可能还没出发,盒子里还是空的
  2. 同学B正在路上,盒子里还是空的也可能有快递了。
  3. 同学B已经取回来了,盒子里有快递了。

根据可能的结果,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;

所有可能的状态变化结果如下:

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

由于对于一个FutureTask对象而言,任务状态可能会被多个线程同时改变,所以 state 是一个线程不安全的变量,使用了volatile修饰,满足线程并发和线程可见性。

sun.misc.Unsafe

从FutureTask整个代码中可以看见,state基本上全是多线程取值操作,没有多线程对state的赋值操作。多线程的赋值操作全是采用的CAS的底层sun.misc.Unsafe类来实现。
在本类的开头有这样一句注释:

 * Style note: As usual, we bypass overhead of using
 * AtomicXFieldUpdaters and instead directly use Unsafe intrinsics.

大意就是java本来提供了采用CAS实现的Atomic类群,但Atomic类群其实底层是用的sun.misc.Unsafe,所以本类绕过了Atomic类直接使用sun.misc.Unsafe,提高效率。
CAS是原意是比较并交换,所以它是两个操作的原子性操作集合。volatile修饰的变量显然不能满足在这种操作下满足原子性。
所以在多线程环境下对volatile修饰的三个变量:

	private volatile int state;
	private volatile Thread runner;
	private volatile WaitNode waiters;

线程安全的取值:依赖volatile可见性;
线程安全的赋值:依赖sun.misc.Unsafe实现的CAS机制

这里注意:
下面两个为什么用volatile修饰,并且WaitNode是什么?

	private volatile Thread runner;
	private volatile WaitNode waiters;

这是因为还是先前的故事,可以发展成更复杂的情况。
同学B就是执行FutureTask的线程,FutureTask就是这个盒子, 同学A就是持有FutureTask引用的线程。开头就描述了FutureTask是一个对Runnable/Callable的包装任务,它只是一个任务管理者,真正执行的任务是FutureTask持有的Runnable/Callable。

看一下FutureTask的run()方法

	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();   // 这里实际是调用的Callable.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);
    }
}

这里面,FutureTask.run 实际上是调用的c.call()执行任务。
为了防止被并发执行。FutureTask对Thread runner做了并发控制,试想如果每个拿到这个FutureTask的线程都去执行这个任务,就会出现并发问题,导致结果与预期不一致。

if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())) 
        return;

// 通过CAS选择一个执行者,并通过volatile告诉其他人已经有人正在执行了。
// 如果要执行也必须等当前执行者完成。因为完成后runner会退出。
finally {
        runner = null;
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }

WaitNode是什么?
在搞清这个问题之前,回到故事原型中。同学A可能将监视器复制一个给同学E,然后同学E可能也复制一个给同学Z…,这样导致会有很多的同学都可以监视当前任务的状态。而反过来,对于FutureTask而言,意味着会有很多的查看者,也就是线程。
为了管理这些的线程,FutureTask持有了一个WaitNode,它是一个内部类:

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

从它的结构可以看出,这是一个简单的单向链表。这些闯入到FutureTask里的线程会根据先后顺序,被管理在WaitNode链表上。为了让可能并发的线程强制形成先后顺序,WaitNode也使用了volatile修饰,

private volatile WaitNode waiters;

并且使用CAS机制处理并发。代码里可见一斑(截取代码片段):

 if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null))

 queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q);
 
 ...

既然可能会有多个线程调用,那么调用的入口在哪里?
那就是get()系列方法.

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

在get()系列方法中,一个没有超时限制,一个有超时限制。它们内部都是通过调用的awaitDone(boolean timed, long nanos)方法来处理调用线程,

private int awaitDone(boolean timed, long nanos) throws InterruptedException {
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    WaitNode q = null;
    boolean queued = false;
    for (;;) {
        if (Thread.interrupted()) {						// 加入中断机制,避免阻塞。
            removeWaiter(q);
            throw new InterruptedException();
        }

        int s = state;
        if (s > COMPLETING) {
            if (q != null)
                q.thread = null;
            return s;
        }
        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) {
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L) {
                removeWaiter(q);
                return state;
            }
            LockSupport.parkNanos(this, nanos);
        }
        else
            LockSupport.park(this);
    }
}

在代码中,整个执行在一个for无限循环中,如果当前线程没有加入到WaitNode队列,将会执行CAS加入,

 queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q);

如果通过超时限制的get()进入方法,则当时间超过限制,会跳出无限循环,否则会执行LockSupport.parkNanos(this, nanos);
而通过没有超时限制的get()进入,会调用LockSupport.park(this);。

好像探索越来越深了,什么是LockSupport.parkNanos(this, nanos) 和 LockSupport.park(this)?

LockSupport.parkNanos() 和 LockSupport.park()

LockSupport是一个对native层 sun.misc.Unsafe方法的包装,提供了LockSupport.parkNanos() 和 LockSupport.park()还有其他几个方法,简单理解,LockSupport.parkNanos()类似与Object.wait(time), 而LockSupport.park()类似于Object.wait(0), 但它们与wait方法有些差异,这里不做展开介绍。
在这个方法里可以理解成, LockSupport.parkNanos()是限时阻塞,而LockSupport.park() 是无限阻塞。
所以,当一个调用futureTask的线程调用了get()方法,如果futureTask结果没有出来,当前线程会被阻塞在这里。被阻塞的线程都在WaitNode链表中限时或者无限沉睡。直到FutureTask异步结果出来调用finishCompletion()方法。

private void finishCompletion() {
    // assert state > COMPLETING;
    for (WaitNode q; (q = waiters) != null;) {
        if (UNSAFE.compareAndSwapObject(this, waitersOffset, 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; // unlink to help gc
                q = next;
            }
            break;
        }
    }

    done();

    callable = null;        // to reduce footprint
}

在finishCompletion()方法中,会通过LockSupport.unpark()唤醒WaitNode链表中的每一个线程,并且释放内存。
这时这些线程才得以从get()方法的无限循环中跳出去。

最后介绍一个方法,cancel(boolean mayInterruptIfRunning)

cancel(boolean mayInterruptIfRunning)

这个方法提供了参数 : 中断正在执行的任务。在方法的实现中,它其实就是调用了Thread.interrupt()方法,给当前线程加上了中断标志而已。 但是mayInterruptIfRunning含义中理解,它有可能无法中断,因为在下达中断命令之前,futureTask任务可能在任何一个可能的状态中。具体是怎样的呢?

public boolean cancel(boolean mayInterruptIfRunning) {
    if (!(state == NEW && UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
              mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
        return false;
    try {    // in case call to interrupt throws exception
        if (mayInterruptIfRunning) {
            try {
                Thread t = runner;
                if (t != null)
                    t.interrupt();
            } finally { // final state
                UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
            }
        }
    } finally {
        finishCompletion();
    }
    return true;
}

从代码中的条件入口判断理解:

 	if (!(state == NEW && UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
              mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
        return false;

在FutureTask中有一个执行线程,假设它是A线程,它负责执行具体的任务。而发出中断指令的是外部线程,可能是同学E,可能是同学Z…, 因此对于futureTask状态的操作存在竞争,所以在这个条件判断中的含义就是:

	NEW 代表当前任务执行结束前。结束可能由四个状态,这在开头提到过。
	* NEW -> COMPLETING -> NORMAL
	* NEW -> COMPLETING -> EXCEPTIONAL
 	* NEW -> CANCELLED
 	* NEW -> INTERRUPTING -> INTERRUPTED

如果当前任务结束之前(可能正在运行,也可能还未开始),并且发出中断执行的线程成功将任务状态切换了,则cancel()将会跳过if判断执行下面的代码,否则无法中断。
如果能够中断:
则当mayInterruptIfRunning为ture,将会调用执行线程的Thread.interrupt(),给执行线程加上了中断标志。
mayInterruptIfRunning为false,则是将当前任务状态直接由NEW转到CANCELLED. 如果线程在调用之前是在执行,则线程在调用之后可能依然在执行,那么任务执行完成后,还能获取到正常的结果吗?
看这个细节:

	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 = null;
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}
protected void set(V v) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = v;
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
        finishCompletion();
    }
}

执行线程执行任务时,线程被阻塞在result = c.call();执行完成后,将会调用 set(result);
而由于执行期间任务状态已经被改为了CANCELLED,这时,set方法中CAS操作将会执行失败,结果无法返回给outcome。
所以这种情况下,是拿不到结果的。

最后

futureTask是多线程编程非常基础和重要的一个类,了解它的机制,相互协作及运行原理,有利于未来多线程编程各种业务下的实战。
如有不正,请多指点。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值