我们在讲源码之前先来一点最基本的应用吧,然后再慢慢的深入。
public class FutureExample1 {
static class MyCallable implements Callable{
@Override
public String call() throws Exception {
System.out.println("do something in callable");
Thread.sleep(5000);
return "done";
}
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
//用法一
FutureTask futureTask = new FutureTask(new MyCallable());
new Thread(futureTask).start();
System.out.println(futureTask.get());
//用法二
ExecutorService executorService = Executors.newCachedThreadPool();
Future future = executorService.submit(new MyCallable());
//如果线程没有执行完,会阻塞在这里
System.out.println(future.get());
}
}
从上面的例子可以得知FutureTask是一个同步工具类,它实现了Future接口,弄懂它有利于我们更加理解java异步操作的实现。
【1】FutureTask所使用的接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
创建线程最主要的就是重写Runnable任务的一个run方法,这个run方法定义了该线程需要做什么事情,这里我们可以发现这个方法并没有返回值,如果我们需要拿到返回值的话,要怎么办呢?
@FunctionalInterface
public interface Callable {
V call() throws Exception;
}
Callable接口提供了一个call()方法作为线程的执行体,对比Callable接口与Runnable接口,最大的不同点在于:
1、Callable有返回值
2、Callable可以抛出异常
那么如何获取结果呢???
public static void main(String[] args) {
Callable myCallable = () -> "This is the results.";
try {
String result = myCallable.call();
System.out.println("Callable 执行的结果是: " + result);
} catch (Exception e) {
System.out.println("There is a exception.");
}
}
简简单单的直接调用call方法就可以获取到执行结果了,但是呢这样有很大的弊端的,这样和直接调用一个方法有啥区别呢???调用call方法执行具体的操作,如果没执行完毕当前线程就会卡在这里了。这时我们会想到那可以直接使用Thread线程变量作为载体嘛,这样不就可以变成异步操作了嘛。可问题是Callable接口不是Runnable接口的子接口,所以Callable对象根本不能直接作为Thread的target,那么应该怎么调用call方法来得到返回结果呢???这时候FutureTask类就出现了哈哈哈,这个类实现了Runnable接口,可以作为Thread的target了,在Runnable的run方法内部再去调用Callable接口的call方法即可,而且该类又实现了Future接口,那么这个接口又是干嘛的呢?
Future接口就是被设计用来获得异步操作的执行结果,它可以用来获取一个操作的执行结果,取消一个操作,判断一个操作是否已经完成或者是否已被取消。
public interface Future {
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
}
Future接口一共定义了5个方法:
get():该方法用来获取执行结果,如果任务还在执行中就阻塞等待。
get(long timeout, TimeUnit unit):该方法同get方法差不多,不同的是最多等待执行的时间,如果指定时间内任务没有完成则会抛出TimeoutException异常。
cancel(boolean mayInterruptIfRunning) :该方法用来尝试取消一个任务的执行,返回boolean类型表示取消操作是否成功。
isCancelled():该方法用于判断任务是否被取消了。如果一个任务在正常执行完成之前被cancel掉了,则返回true。
isDone():如果一个任务已经结束,则返回true。注意下面三种情况都是代表了任务结束:
【1】任务正常执行完毕
【2】任务抛出了异常
【3】任务已经被取消了
其实cancel操作返回true不一定代表任务真的就被取消了。这要取决于发动cancel方法时候任务所处的状态:
【1】如果发起cancel时任务还没开始运行,那么直接就被取消了,随后任务不会再执行了
【2】如果发起cancel时任务已经在运行的话,那么就取决于mayInterruptIfRunning参数了,如果mayInterruptIfRunning参数为true,则当前正在执行的任务被中断,如果为false则允许正在执行的任务继续执行,直到它执行完成。
RunnableFuture接口其实就是同时继承了Runnnable接口和Future接口
public interface RunnableFuture extends Runnable, Future {
void run();
}
而恰恰好FutureTask就是实现了该接口,也就是说FutureTask实现了Runnable接口一方面可以作为任务体执行,另一方面它又实现了Future接口可以来获取获取操作的执行结果,或者判断任务是否执行完毕,取消任务的执行等等。所以简单点说,FutureTask本质上就是一个Task,我们可以把它当做一个Runnable对象来使用就行了。但是它同时又实现了Future接口,因此我们可以进行一些额外的操作。
接下来我们开始分析FutureTask源码,将从三个方面为切入点进行分析源码:状态、队列、CAS
//状态
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 callable;
private Object outcome;
private volatile Thread runner;
//队列
private volatile WaitNode waiters;
//CAS操作
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);
}
}
【1】state:状态属性表示的且是volatile类型的,确保了不同线程对它修改的可见性。state属性是贯穿了整个FutureTask的最核心属性,该属性的值代表了任务在运行过程中的状态,随着任务的执行,状态不断的进行转变。七个状态包含了1个初始化状态,2个中间态以及4个 终止态。
任务的初始化状态是NEW,这个在构造函数初始化的时候就设置了。
任务的终止状态有4种:
NORMAL : 任务正常执行完毕
EXCEPTIONAL : 任务执行过程种发生异常
CANCELLED : 任务被取消
INTERRUPTED : 任务被中断
任务的中间态有2种:
COMPLETING : 正在设置任务结果
INTERRUPTING : 正在中断运行任务的线程
这里要切记其实中间态只是一瞬间而已,非常的短暂。而且中间态不代表任务正在执行中,而是任务已经执行完毕了,正在设置执行的返回结果,也就是说只要state不处于NEW状态的话,就说明任务已经执行完毕了。
将一个任务的状态设置为终止态有以下三种方法:
1、set
2、setExcetion
3、cancel
我们后面分析源码的时候会讲到的。
【2】callable : 代表了要执行的任务本身也就是FutureTask的task部分,这里之所以使用callable而不使用runnable是因为FutureTask实现了Future接口,后面要获取执行的结果,所以就使用了callable。
【3】outcome : 属性代表了任务的执行结果或者抛出的异常,所以该类型为Object类型。通常就是FutureTask的run方法调用call()方法返回结果设置到outcome属性中,这样我们就可以获取到了。
【4】runner : 代表了当前执行FutureTask中任务的线程,为什么需要记录该线程呢???这是为了中断获取cancel任务的时候可以用到,只有知道了执行该任务的线程是哪个,我们才可以去中断它呢。
【5】waiters : 本质上就是一个单向链表的队列,它表示所有正在等待任务执行完毕的一个线程集合,也就是这些线程都是在等待任务执行完毕。如果我们去get()获取结果的时候,当前任务还没有执行完毕怎么办呢???那么获取结果的线程就会在等待队列中被挂起,直到任务执行完毕后被唤醒。接下来我们看下每个节点的一个结构:
static final class WaitNode {
volatile Thread thread;
volatile WaitNode next;
WaitNode() { thread = Thread.currentThread(); }
}
其实挺简单的,它就包含了记录线程的thread属性和指向下一个节点的next属性。它其实是当做栈来使用的,并使用CAS来完成入栈和出栈操作。那么为啥要使用一个线程安全的栈呢?因为同一时刻可能有多个线程在获取任务的执行结果,结果任务还在执行中,那么我们此时肯定就要把这些线程封装成waitNode结果然后扔到栈中,也就是完成入栈操作。当然的也可能出现多个线程同时出栈的情况,因此需要使用CAS操作来保证入栈以及出栈的线程安全。这里FutureTask只需要使用一个waiters属性即可,也就是一个指向栈顶的指针就行,其实就是链表的头结点。
【6】CAS操作:一般我们在静态代码块中就需要初始化CAS操作的属性的偏移量,从上面代码中得知,CAS操作主要针对3个属性 : runner、state、waiters,说明这3个属性基本是会被多个线程同时访问的,所以必须保证线程安全性。
那么,要怎么获得目标field
的内存偏移量offset
呢? Unsafe类为我们提供了一个方法:
public native long objectFieldOffset(Field field);
该方法的参数是我们进行CAS操作的field对象,那么怎么获取这个field对象呢?最直接的办法就是通过反射
Class> k = FutureTask.class;
Field stateField = k.getDeclaredField("state");
这样我们就可以直接对FutureTask的state属性进行操作了。
分析完了上面的基本属性,我们接下来看看FutureTask的构造函数:
public FutureTask(Callable callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
FutureTask一共有两个构造函数,这两个构造函数一个直接传入callable对象,一个是传入Runnable对象以及一个result结果,然后通过Executors工具类将它适配为Callable对象,所以这两个构造函数基本是一样的。
【1】run方法(实现了Runnable接口的run方法)
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable 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);
}
}
首先我们判断当前状态是不是NEW,如果不为NEW的话表示当前任务已经执行完毕,那么直接return。如果当前状态为NEW的话则使用CAS操作将runner属性设置为当前线程,也就是记录执行当前任务的线程,compareAndSwapObject也就是cas操作。接下来我们就调用了Callable对象的call方法来执行任务,如果任务执行成功,则调用set(result)来设置结果,否则使用setException(ex)来设置抛出的异常。
接下来我们看下set(result)方法:
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
该方法表示已经执行完成了,然后开始使用CAS操作设置当前的state属性由原来的NEW状态为COMPLETING状态,其实该状态是一个非常短暂的中间状态,表示正在设置执行结果。状态设置成功后,就把任务结果赋值给outcome,然后再把state属性变为NORMAL状态,这里没有比较设置哦,是直接进行设置。由于state属性被设为volatile,所以保证了state状态对其他线程的可见性。然后调用finishCompletion()方法来完成执行结果的设置。
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
}
首先看该方法其实就是把waiters属性由原来的值设置为null,在前面可以得知waiters是指向栈的顶节点,现在将它置为null的目的就是清空整个栈。如果设置不成功也就是if语句不会被执行,那么就继续下一次循环,而下一次循环的条件是waiters!=null,也就是当前的waiters还不会被其他线程设置为Null,那么我们继续执行CAS操作直到成功为止。其实这个for循环只是为了确保waiters被成功设置为null后可以跳出循环而已,也就是我们经常说的自旋操作。将waiters属性设置为null之后就进行for(;;)循环了,这才是真正的遍历节点,也就是普通的遍历链表的操作,我们前面有讲到了waitNode存放了当前等待执行任务结束的线程,这个循环的作用是当前任务已经执行完毕了,遍历所有等待的线程并唤醒他们。
然后就执行done()方法,这个方法是空方法,留给子类去覆写的,以实现一些任务执行结束前的额外操作,然后就是把callable属性也清空了。
最后我们又回到了run方法来了,run方法最后执行了finally模块,首先把runner属性设置为null,然后这时候检查有没有遗漏的中断呢??如果发现s >= interrupting,说明执行任务的线程有可能被中断了,因为s >= interrupting只有两种可能,state状态为interrupting以及Interrupted了。那么我们不仅会想了我们前面不是已经set方法或者setException方法将state状态设置为NORMAL或者EXCEPTIONAL了吗?怎么还会出现中断状态呢??要切记我们在多线程的环境中哦,也许当前线程执行run方法的时候,此时还没有执行set()方法哦,然后其他线程取消了任务的执行,也就是把state状态改写为interrupting或者Interrupted状态了,那么此时当前线程执行完的时候再去run()方法的时候就会发觉CAS操作根据设置不了了,因为当前的state预期值已经不是NEW状态
UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)
关于cancel方法我们在后面再进行讲解吧。我们还是回到当前代码中来,如果当前状态为中断状态呢,那么我们就执行以下方法:
private void handlePossibleCancellationInterrupt(int s) {
// It is possible for our interrupter to stall before getting a
// chance to interrupt us. Let's spin-wait patiently.
if (s == INTERRUPTING)
while (state == INTERRUPTING)
Thread.yield(); // wait out pending interrupt
}
判断当前state状态如果为中断中间态,那么不要马上就退出run方法,而是应该等待状态变为中断终止态才代表执行完毕可以退出方法,上面是使用了让出当前线程然后继续原地自旋做判断。
总结:
NORMAL
或者EXCEPTIONAL
)【2】cancel(boolean mayInterruptlfRunning)
分析了上面的run方法,那么我们紧接着就来分析取消任务的方法,看看一个任务是怎么被别的线程取消的。
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;
}
首先判断当前的状态是否为NEW,如果不为NEW说明已经被执行完毕或者已经取消了,那么直接返回false。
如果此时状态还为NEW的话,那么我们接着做CAS操作,UNSAFE.compareAndSwapInt(this, stateOffset, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED),也就是根据mayInterruptIfRunning的值将state状态设置为INTERRUPTING或者CANCELLED状态,然后就可以执行后面的try语句了,但最后无论如何都是返回true(无论是否取消了任务)。
我们继续往下看try方法做了什么操作呢?
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();
}
就是判断当前mayInterruptIfRunning参数是否为true,来确定下一步如何操作
1、如果此时为false也就是说此时状态为CANCELED,那么无需做任何操作,程序继续往下执行
2、如果此时为false也就是说此时状态为INTERRUPTING,最后变为INTERRUPTED。
对于第一种情况,虽然cancel方法最终返回了true,但它只是简单的将state状态设置为CANCELED而已,并没有中断线程的执行。但是这样带来的后果就是,任务即使执行完毕了,也无法去设置任务的执行结果(set方法)。很简单我们之前在分析run方法的时候使用set方法来设置结果,但是采用的是CAS操作,而且当前的状态必须为NEW状态,如果不为NEW则不设置中间态状态了,也就设置不了结果值了。
对于第二种情况,我们会设置runner(当前执行该任务的线程)中断标志位,但是该线程响应不响应呢我们是控制不了的,只能由执行任务的线程自己决定,也就是重写了call()方法的具体执行体决定的,程序员自己决定是否对中断标志位进行响应,是否抛出中断异常。在FutureTask的run方法中有对抛出异常的情况做处理的并调用setException()方法,这里中断异常又分成了两种情况:
1、cancel操作,然后用户在call方法里面响应中断后抛出异常,这时catch (Throwable ex){}
代码块会捕捉异常并且执行setException(ex)操作,但是这个操作肯定是失败的,因为是采用了CAS操作,并且前提是当前状态为NEW:
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING))
所以在这里最大的意义不是为了对中断进行处理,而是让出资源而已。
2、自己在call方法中正常执行失败而抛出的异常,此时state状态仍为NEW,则这个异常会被设置到outcome中。
反正无论如何FutureTask的run方法都会执行到finally方法块,这时候如果发现s == interrupting,说明cancel方法还没设置为终止状态,那么就必须等待直到为中断终止态。这样子cancel方法和run方法的分析对接上了。
【3】get方法 获取执行结果
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
当前任务还没有执行完毕或者正在设置执行结果的话,我们就使用awaitDone方法等待任务直到其变为终止状态,这里注意awaitDone方法返回值是任务的状态而不是结果,最后我们再根据状态来计算结果。接下来我们看下awaitDone源码:
在分析源码之前有一点我们先说明一下,FutureTask中会涉及到两种线程,一种是执行任务的线程,它只有一个,FutureTask类的run方法就是由它执行的。另一种线程是专门获取任务执行结果的,它可以有很多个,这些线程都是可并发执行的,都可以调用get方法来获取任务的执行结果。但是如果当前任务还没有执行完的话,这些线程就需要进入waiterNode栈中进行挂起,直到任务执行结束,或者任务被中断。
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
//自循环
for (;;) {
//首先检测线程是否被设置中断标志位,如果等待的任务还没有执行完的话则调用get方法会被
//阻塞并扔到waiters栈中挂起等待。如果任务迟迟没有执行完毕,可以直接中断在waiters栈
//中的线程
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
//如果任务已经进入终止状态(s > COMPLETING)的话,置当前的waitNode的线程为null,然
//后返回当前任务的状态
int s = state;
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
//否则如果任务正在设置执行结果,我们就让出CPU的资源继续等待
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
//再否则就说明线程还没有执行完成,判断此时q是否为null,如果为空说明当前线程还没有入
//栈等待队列,那么我们新建一个waitNode节点,也就是记录当前线程的一个节点
else if (q == null)
q = new WaitNode();
//如果q不为null了,说明waiterNode节点被创建出来了。如果queued为false,说明此时还没
//有入队,下面CAS操作是将新建的q节点添加到原来waiters的头结点之前,也就是入栈操作。
else if (!queued)
//q.next = waiters; 当前节点的next节点指向当前的栈顶元素
//期望值就是如果当前的栈顶节点在这个过程没有变的话,既没有发生并发入栈情况
//那么就执行最终值,也就是waiters = q,也就是waiters指针指向了q节点。
//保证了同一时刻如果有多个线程在同时入栈,只有一个能够操作成功。
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
//如果上面的条件还是不满足的话,那么判断此时是否设置timed,也就是超时限制
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos);
}
//若是执行到这一步了,那么就是把获取执行结果的线程挂起了(挂在了栈中)
else
LockSupport.park(this);
}
}
那么什么时候这个线程才会被唤醒呢??
1、任务执行完毕之后,在finishCompletion方法中会唤醒所有在栈中等待的线程
2、等待的线程自身因为被中断等原因而被唤醒
然后如果此时被唤醒的话,会继续下一轮的循环,首先是检查中断,此时q已经不为null了,因此在有中断发生的情况下,抛出中断之前还多了一步removeWaiter(q)操作,该操作是将当前线程从等待的栈中移除,相比入栈操作出栈稍微复杂一点:
private void removeWaiter(WaitNode node) {
if (node != null) {
//首先设置要出栈的waitNode节点为NULL,其实这里相当于一个标记而已
node.thread = null;
retry:
for (;;) { // restart on removeWaiter race
for (WaitNode pred = null, q = waiters, s; q != null; q = s) {
//s为头结点的下一个节点
s = q.next;
//判断当前节点是否不为空(null就是移除的标记)
if (q.thread != null)
pred = q;
//如果q为NULL,也就是该节点将会被移除的,此时判断pred是否为空,如果第一次循环的
//话,pred为NULL的不成立,如果第二次以后循环的话pred不为Null,该条件成立
else if (pred != null) {
//此时就把pred节点(当前节点的pre节点)指向s节点(当前节点的next节点),也
//就是删除了p节点了
pred.next = s;
//这里如果pred的Thread(volatile修饰,保证可见性)为null,表明有可能这个
//pred节点可能被其他线程标志了,将会被拿出waiters链表,那么我们此时不必要
//再循环下去了,而是重头来了
if (pred.thread == null) // check for race
continue retry;
}
//如果刚好要删除的节点位于栈顶,那么直接waiters指向s节点,如果操作不成功的话,
//会回到retry处重来,即使成功了程序依旧会遍历完整个节点,把node.thread为Null的
//全部删除掉
else if (!UNSAFE.compareAndSwapObject(this, waitersOffset, q, s))
continue retry;
}
break;
}
}
}
我们可以看到上面虽然我们传入了一个需要剔除的节点,但事实上它可能剔除的不止是传入的节点,而是所有已经被标记为null的节点都会被剔除,这样不仅清除容易(不需要专门去定位传入的node在哪里),也提升了效率(一下子清除掉所有被标记的节点)。
我们回到awaitdone方法上面来,如果此时线程不是因为中断而被唤醒,那么则会继续往下执行,此时q不为null了,queued为true了,不需要再入栈了。否则就是原地继续自旋等待(s==COMPLETING的时候是调用Thread.yield或者由于对应任务还没有执行完成的情况下直接被挂起),否则就等到执行执行完毕会唤醒挂起的线程。至此get方法就分析完毕了。为什么awaitDone方法和get方法都没有加锁呢,它不怕产生线程安全问题吗??其实整个方法内部都使用了局部变量,没有线程安全问题的。对于全局共享的变量waiters的修改,也会使用CAS操作,保证了线程安全,而state属性本身是volatile修饰的,保证了读取的时候可见性,所以不用加锁也是安全的。