Runnable
Runnable接口只有一个run函数,该函数没有返回值。Thread类在调用start()函数后就是执行的Runnable的run()方法。其声明如下:
public interface Runnable {
public abstract void run();
}
但是, 可以发现, 这个方法并没有任何返回值.
如果我们希望执行某种类型的操作并拿到它的执行结果, 该怎么办呢?
Callable
Callable与Runnable的功能大致相似,它有一个call方法,该方法有返回值,其申明如下:
public interface Callable {
V call() throws Exception;
}
使用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方法可能是一个特别耗时的操作, 这将导致程序停在myCallable.call()调用处, 无法继续运行, 直到call方法返回。
- 如果call方法始终不返回, 我们没办法中断它的运行。
因此, 理想的操作应当是, 我们将call方法提交给另外一个线程执行, 并在合适的时候, 判断任务是否完成, 然后获取线程的执行结果或者撤销任务, 这种思路的实现就是Future接口:
Future
Future就是对于具体的Runnable和Callable任务的执行结果进行取消、查询是否完成、获取结果、设置结果等。其中get方法会阻塞知道任务返回结果。其声明如下:
public interface Future {
/**
* Attempts to cancel execution of this task. This attempt will
* fail if the task has already completed, has already been cancelled,
* or could not be cancelled for some other reason. If successful,
* and this task has not started when cancel is called,
* this task should never run. If the task has already started,
* then the mayInterruptIfRunning parameter determines
* whether the thread executing this task should be interrupted in
* an attempt to stop the task. *
*/
boolean cancel(boolean mayInterruptIfRunning);
/**
* Returns true if this task was cancelled before it completed
* normally.
*/
boolean isCancelled();
/**
* Returns true if this task completed.
*
*/
boolean isDone();
/**
* Waits if necessary for the computation to complete, and then
* retrieves its result.
*
* @return the computed result
*/
V get() throws InterruptedException, ExecutionException;
/**
* Waits if necessary for at most the given time for the computation
* to complete, and then retrieves its result, if available.
*
* @param timeout the maximum time to wait
* @param unit the time unit of the timeout argument
* @return the computed result
*/
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
在Future接口中声明了5个方法,下面依次解释每个方法的作用:
cancel方法用来取消任务,如果取消任务成功则返回true,取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
isDone方法表示任务是否已经完成,若任务完成,则返回true;
get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。
实际上Future提供了三种功能:
- 判断任务是否完成;
- 中断任务
3 获取任务的执行结果
FutureTask
Future只是一个接口,无法直接创建对象,所以有了FutureTask,FutureTask实现了RunnableFuture
所以简单来说,FutureTask本质上就是一个“Task”,我们可以把它当做简单的Runnable对象来使用。但是它又同时实现了Future接口,因此我们可以对它所代表的“Task”进行额外的控制操作。
public class FutureTask implements RunnableFuture
public interface RunnableFuture extends Runnable, Future {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
FutureTask还可以包装Runnable和Callable,由构造函数注入依赖。
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
}
Runnable注入会被Executors.callable()函数转换为Callable类型,也就是说FutureTask最终都是执行Callable类型的任务,该适配函数实现如下:
public static Callable callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter(task, result);
}
/**
* A callable that runs given task and returns given result
*/
static final class RunnableAdapter implements Callable {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}
FutureTask实现了Runnable,因此它既可以通过Thread包装来直接执行,也可以提交给ExecutorService来执行。
Unsafe
Unsafe类对于并发编程来说是个很重要的类,如果你稍微看过J.U.C里的源码,你会发现到处充斥着这个类的方法调用。这个类的最大的特点在于,它提供了硬件级别的CAS原子操作。
CAS可以说是实现了最轻量级的锁,当多个线程尝试使用CAS同时更新同一个变量时,只有其中的一个线程能成功地更新变量的值,而其他的线程将失败。然而,失败的线程并不会被挂起。
CAS操作包含了三个操作数: 需要读写的内存位置,进行比较的原值,拟写入的新值。
在Unsafe类中,实现CAS操作的方法是: compareAndSwapXXX
public native boolean compareAndSwapObject(Object obj, long offset, Object expect, Object update);
obj是我们要操作的目标对象
offset表示了目标对象中,对应的属性的内存偏移量
expect是进行比较的原值
update是拟写入的新值。
所以该方法实现了对目标对象obj中的某个成员变量(field)进行CAS操作的功能。
那么,要怎么获得目标field的内存偏移量offset呢? Unsafe类为我们提供了一个方法:
public native long objectFieldOffset(Field field);
该方法的参数是我们要进行CAS操作的field对象,要怎么获得这个field对象呢?最直接的办法就是通过反射了:
Class> k = FutureTask.class;
Field stateField = k.getDeclaredField("state");
这样一波下来,我们就能对FutureTask的state属性进行CAS操作了.
除了compareAndSwapObject,Unsafe类还提供了更为具体的对int和long类型的CAS操作:
public native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);
public native boolean compareAndSwapLong(Object obj, long offset, long expect, long update);
从方法签名可以看出,这里只是把目标field的类型限定成int和long类型,而不是通用的Object.
原理讲解
Runnable和Callable描述的都是抽象的计算任务,这些任务通常是有生命周期的,由于有些任务可能要执行很长的时间,因此通常希望可以取消这些任务。而Future用来表示一个任务的生命周期,并提供方法来判断任务是否已经完成或取消,以及获取任务的结果等。Future是接口,无法直接创建对象,所以才有了FutureTask,而FutureTask之所以能支持cancel操作,是因为FutureTask有两个很重要的属性state和runner。
关于Java并发工具类的三板斧: 状态,队列,CAS
以这三个方面为切入点来看源码,有助于我们快速的看清FutureTask的概貌:
状态
在FutureTask中,状态是由state属性来表示的,不出所料,它是volatile类型的,确保了不同线程对它修改的可见性:
private volatile int state; // 注意volatile关键字
/**
* 在构建FutureTask时设置,同时也表示内部成员callable已成功赋值,
* 一直到worker thread完成FutureTask中的run();
*/
private static final int NEW = 0;
/**
* woker thread在处理task时设定的中间状态,处于该状态时,
* 说明worker thread正准备设置result.
*/
private static final int COMPLETING = 1;
/**
* 当设置result结果完成后,FutureTask处于该状态,代表过程结果,
* 该状态为最终状态final state,(正确完成的最终状态)
*/
private static final int NORMAL = 2;
/**
* 同上,只不过task执行过程出现异常,此时结果设值为exception,
* 也是final state
*/
private static final int EXCEPTIONAL = 3;
/**
* final state, 表明task被cancel(task还没有执行就被cancel的状态).
*/
private static final int CANCELLED = 4;
/**
* 中间状态,task运行过程中被interrupt时,设置的中间状态
*/
private static final int INTERRUPTING = 5;
/**
* final state, 中断完毕的最终状态,几种情况,下面具体分析
*/
private static final int INTERRUPTED = 6;
state属性是贯穿整个FutureTask的最核心的属性,该属性的值代表了任务在运行过程中的状态,随着任务的执行,状态将不断地进行转变,从上面的定义中可以看出,总共有7种状态:包括了1个初始态,2个中间态和4个终止态。
state有四种可能的状态转换:
NEW -> COMPLETING -> NORMAL
NEW -> COMPLETING -> EXCEPTIONAL
NEW -> CANCELLED
NEW -> INTERRUPTING -> INTERRUPTED
任务的初始状态都是NEW, 这一点是构造函数保证的,我们后面分析构造函数的时候再讲;
任务的终止状态有4种:
NORMAL:任务正常执行完毕
EXCEPTIONAL:任务执行过程中发生异常
CANCELLED:任务被取消
INTERRUPTED:任务被中断
任务的中间状态有2种:
COMPLETING 正在设置任务结果
INTERRUPTING 正在中断运行任务的线程
值得一提的是,任务的中间状态是一个瞬态,它非常的短暂。而且任务的中间态并不代表任务正在执行,而是任务已经执行完了,正在设置最终的返回结果,所以可以这么说:
只要state不处于 NEW 状态,就说明任务已经执行完毕
注意,这里的执行完毕是指传入的Callable对象的call方法执行完毕,或者抛出了异常。所以这里的COMPLETING的名字显得有点迷惑性,它并不意味着任务正在执行中,而意味着call方法已经执行完毕,正在设置任务执行的结果。
而将一个任务的状态设置成终止态只有三种方法:
set
setException
cancel
队列
在FutureTask中,队列的实现是一个单向链表,它表示所有等待任务执行完毕的线程的集合。我们知道,FutureTask实现了Future接口,可以获取“Task”的执行结果,那么如果获取结果时,任务还没有执行完毕怎么办呢?那么获取结果的线程就会在一个等待队列中挂起,直到任务执行完毕被唤醒。
在并发编程中使用队列通常是将当前线程包装成某种类型的数据结构扔到等待队列中,我们先来看看队列中的每一个节点是怎么个结构:
static final class WaitNode {
volatile Thread thread;
volatile WaitNode next;
WaitNode() { thread = Thread.currentThread(); }
}
相比于AQS的sync queue所使用的双向链表中的Node,这个WaitNode要简单多了,它只包含了一个记录线程的thread属性和指向下一个节点的next属性。
值得一提的是,FutureTask中的这个单向链表是当做栈来使用的,确切来说是当做Treiber栈来使用的,不了解Treiber栈是个啥的可以简单的把它当做是一个线程安全的栈,它使用CAS来完成入栈出栈操作。为啥要使用一个线程安全的栈呢,因为同一时刻可能有多个线程都在获取任务的执行结果,如果任务还在执行过程中,则这些线程就要被包装成WaitNode扔到Treiber栈的栈顶,即完成入栈操作,这样就有可能出现多个线程同时入栈的情况,因此需要使用CAS操作保证入栈的线程安全,对于出栈的情况也是同理。
由于FutureTask中的队列本质上是一个Treiber栈,那么使用这个队列就只需要一个指向栈顶节点的指针就行了,在FutureTask中,就是waiters属性:
/** Treiber stack of waiting threads */
private volatile WaitNode waiters;
事实上,它就是整个单向链表的头节点。
综上,FutureTask中所使用的队列的结构如下:
CAS操作
CAS操作大多数是用来改变状态的,在FutureTask中也不例外。我们一般在静态代码块中初始化需要CAS操作的属性的偏移量:
// Unsafe mechanics
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);
}
}
从这个静态代码块中我们也可以看出,CAS操作主要针对3个属性,包括state、runner和waiters,说明这3个属性基本是会被多个线程同时访问的。其中state属性代表了任务的状态,waiters属性代表了指向栈顶节点的指针,这两个我们上面已经分析过了。runner属性代表了执行FutureTask中的“Task”的线程。为什么需要一个属性来记录执行任务的线程呢?这是为了中断或者取消任务做准备的,只有知道了执行任务的线程是谁,我们才能去中断它。
定义完属性的偏移量之后,接下来就是CAS操作本身了。在FutureTask,CAS操作最终调用的还是Unsafe类的compareAndSwapXXX方法.
核心属性
首先我们先来看看FutureTask的几个核心属性:
/**
* The run state of this task, initially NEW. The run state
* transitions to a terminal state only in methods set,
* setException, and cancel. During completion, state may take on
* transient values of COMPLETING (while outcome is being set) or
* INTERRUPTING (only while interrupting the runner to satisfy a
* cancel(true)). Transitions from these intermediate to final
* states use cheaper ordered/lazy writes because values are unique
* and cannot be further modified.
*
* Possible state transitions:
* NEW -> COMPLETING -> NORMAL
* NEW -> COMPLETING -> EXCEPTIONAL
* NEW -> CANCELLED
* NEW -> INTERRUPTING -> INTERRUPTED
*/
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;
/** The underlying callable; nulled out after running */
private Callable callable;
/** The result to return or exception to throw from get() */
private Object outcome; // non-volatile, protected by state reads/writes
/** The thread running the callable; CASed during run() */
private volatile Thread runner;
/** Treiber stack of waiting threads */
private volatile WaitNode waiters;
可以看出,FutureTask的核心属性只有5个:
state
callable
outcome
runner
waiters
关于 state waiters runner三个属性我们上面已经解释过了。剩下的callable属性代表了要执行的任务本身,即FutureTask中的“Task”部分,为Callable类型,这里之所以用Callable而不用Runnable是因为FutureTask实现了Future接口,需要获取任务的执行结果。
outcome属性代表了任务的执行结果或者抛出的异常,为Object类型,也就是说outcome可以是任意类型的对象,所以当我们将正常的执行结果返回给调用者时,需要进行强制类型转换,返回由Callable定义的V类型。
这5个属性综合起来就完成了整个FutureTask的工作,使用关系如下
任务本尊:callable
任务的执行者:runner
任务的结果:outcome
获取任务的结果:state + outcome + waiters
中断或者取消任务:state + runner + waiters
创建一个FutureTask首先调用构造方法, 这是state设置为初始态NEW, 当创建完一个Task通常会提交给Executors或者Thread来执行,最终会调用Task的run方法,
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
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);
}
}
FutureTask的run方法首先会判断任务的状态,如果任务状态不是NEW,说明任务状态已经改变,说明已经走了上面4种可能变化的一种,比如调用了cancel,此时状态为Interrupting。
如果状态是NEW,判断runner是否为null,如果为null,则把当前执行任务的线程赋值给runner,如果runner不为null,说明已经有线程在执行,直接返回。这里使用cas来赋值worker thread是保证多个线程同时提交同一个FutureTask时,确保该FutureTask的run方法只被调用一次。
!UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())
语义相当于
if (this.runner == null ){
this.runner = Thread.currentThread();
}
接着开始执行任务,如果要执行的任务不为空,并且state为New就执行,调用Callable的call方法,如果执行成功则set结果,如果出现异常则setException。最后把runner设为null。
set方法:如果现在的状态是NEW就把状态设置成cCOMPLETING,然后再设置成NORMAL,state的状态变化就是:NEW->COMPLETING->NORMAL.最后执行finishCompletion()方法。
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状态,我们在一开始介绍state状态的时候说过,COMPLETING是一个非常短暂的中间态,表示正在设置执行的结果。
接下来我们再来看看发生了异常的版本setException(ex):
protected void setException(Throwable t) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t;
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
}
}
可见,除了将outcome属性赋值为异常对象,以及将state的终止状态修改为EXCEPTIONAL,其余都和set方法类似。在方法的最后,都调用了 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
}
这个方法事实上完成了一个“善后”工作。我们先来看看if条件语句中的CAS操作:
UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)
该方法是将waiters属性的值由原值设置为null, 我们知道,waiters属性指向了Treiber栈的栈顶节点,可以说是代表了整个Treiber栈,将该值设为null的目的就是清空整个栈。如果设置不成功,则if语句块不会被执行,又进行下一轮for循环,而下一轮for循环的判断条件又是waiters!=null ,由此我们知道,虽然最外层的for循环乍一看好像是什么遍历节点的操作,其实只是为了确保waiters属性被成功设置成null,本质上相当于一个自旋操作。
将waiters属性设置成null以后,接下了 for (;;)死循环才是真正的遍历节点,可以看出,循环内部就是一个普通的遍历链表的操作,我们前面讲属性的时候说过,Treiber栈里面存放的WaitNode代表了当前等待任务执行结束的线程,这个循环的作用也正是遍历链表中所有等待的线程,并唤醒他们。
将Treiber栈中所有挂起的线程都唤醒后,下面就是执行done方法:
/**
* Protected method invoked when this task transitions to state
* {@code isDone} (whether normally or via cancellation). The
* default implementation does nothing. Subclasses may override
* this method to invoke completion callbacks or perform
* bookkeeping. Note that you can query status inside the
* implementation of this method to determine whether this task
* has been cancelled.
*/
protected void done() { }
这个方法是一个空方法,从注释上看,它是提供给子类覆写的,以实现一些任务执行结束前的额外操作。
run方法最后还有一个finally块:
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);
}
在finally块中,我们将runner属性置为null,并且检查有没有遗漏的中断,如果发现s >= INTERRUPTING, 说明执行任务的线程有可能被中断了,因为s >= INTERRUPTING 只有两种可能,state状态为INTERRUPTING和INTERRUPTED。
有的同学可能就要问了,咱前面已经执行过的set方法或者setException方法不是已经将state状态设置成NORMAL或者EXCEPTIONAL了吗?
怎么会出现INTERRUPTING或者INTERRUPTED状态呢?别忘了,咱们在多线程的环境中,在当前线程执行run方法的同时,有可能其他线程取消了任务的执行,此时其他线程就可能对state状态进行改写,这也就是我们在设置终止状态的时候用putOrderedInt方法,而没有用CAS操作的原因——我们无法确信在设置state前是处于COMPLETING中间态还是INTERRUPTING中间态。
/**
* Ensures that any interrupt from a possible cancel(true) is only
* delivered to a task while in run or runAndReset.
*/
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状态是INTERRUPTING,我们在原地自旋,直到state状态转换成终止态。
run方法重点做了以下几件事:
将runner属性设置成当前正在执行run方法的线程
调用callable成员变量的call方法来执行任务
设置执行结果outcome, 如果执行成功, 则outcome保存的就是执行结果;如果执行过程中发生了异常, 则outcome中保存的就是异常,设置结果之前,先将state状态设为中间态
对outcome的赋值完成后,设置state状态为终止态(NORMAL或者EXCEPTIONAL)
唤醒Treiber栈中所有等待的线程
善后清理(waiters, callable,runner设为null)
检查是否有遗漏的中断,如果有,等待中断状态完成。
我们前面说“state只要不是NEW状态,就说明任务已经执行完成了”就体现在这里,因为run方法中,我们是在c.call()执行完毕或者抛出了异常之后才开始设置中间态和终止态的。
Future接口的实现
Future接口一共定义了5个方法,我们一个个来看:
cancel(boolean mayInterruptIfRunning)
既然上面在分析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;
}
- 如果发起cancel时任务还没有开始运行,则随后任务就不会被执行;
- 如果发起cancel时任务已经在运行了,则这时就需要看mayInterruptIfRunning参数了:
2.1 如果mayInterruptIfRunning 为true, 则当前在执行的任务会被中断
2.2 如果mayInterruptIfRunning 为false, 则可以允许正在执行的任务继续运行,直到它执行完
对于“任务已经执行完成了或者任务已经被取消过了,则cancel操作一定是失败的(返回false)”这两条,是通过简单的判断state值是否为NEW实现的,因为我们前面说过了,只要state不为NEW,说明任务已经执行完毕了。从代码中可以看出,只要state不为NEW,则直接返回false。
如果state还是NEW状态,我们再往下看:
UNSAFE.compareAndSwapInt(this, stateOffset, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED)
这一段是根据mayInterruptIfRunning的值将state的状态由NEW设置成INTERRUPTING或者CANCELLED,当这一操作也成功之后,就可以执行后面的try语句了,但无论怎么,该方法最后都返回了true。
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();
}
我们知道,runner属性中存放的是当前正在执行任务的线程,因此,这个try块的目的就是中断当前正在执行任务的线程,最后将state的状态设为INTERRUPTED,当然,中断操作完成后,还需要通过finishCompletion()来唤醒所有在Treiber栈中等待的线程。
我们现在总结一下,cancel方法实际上完成以下两种状态转换之一:
- NEW -> CANCELLED (对应于mayInterruptIfRunning=false)
- NEW -> INTERRUPTING -> INTERRUPTED (对应于mayInterruptIfRunning=true)
对于第一条路径,虽说cancel方法最终返回了true,但它只是简单的把state状态设为CANCELLED,并不会中断线程的执行。但是这样带来的后果是,任务即使执行完毕了,也无法设置任务的执行结果,因为前面分析run方法的时候我们知道,设置任务结果有一个中间态,而这个中间态的设置,是以当前state状态为NEW为前提的。
对于第二条路径,则会中断执行任务的线程,我们在倒回上面的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);
}
}
虽然第二条路径中断了当前正在执行的线程,但是,响不响应这个中断是由执行任务的线程自己决定的,更具体的说,这取决于c.call()方法内部是否对中断进行了响应,是否将中断异常抛出。
然而,值得一提的是,即使这里进入了catch (Throwable ex){}代码块,setException(ex)的操作一定是失败的,因为在我们取消任务执行的线程中,我们已经先把state状态设为INTERRUPTING了,而setException(ex)的操作要求设置前线程的状态为NEW。
既然这个setException(ex)的操作一定是失败的,那放在这里有什么用呢?事实上,这个setException(ex)是用来处理任务自己在正常执行过程中产生的异常的,在我们没有主动去cancel任务时,任务的state状态在执行过程中就会始终是NEW,如果任务此时自己发生了异常,则这个异常就会被setException(ex)方法成功的记录到outcome中。
反正无论如何,run方法最终都会进入finally块,而这时候它会发现s >= INTERRUPTING,如果检测发现s = INTERRUPTING,说明cancel方法还没有执行到中断当前线程的地方,那就等待它将state状态设置成INTERRUPTED.
说完了cancel,我们再来看看 isCancelled()方法,相较而言,它就简单多了:
public boolean isCancelled() {
return state >= CANCELLED;
}
那么state >= CANCELLED 包含了那些状态呢,它包括了: CANCELLED INTERRUPTING INTERRUPTED
isDone()
与 isCancelled方法类似,isDone方法也是简单地通过state状态来判断。
public boolean isDone() {
return state != NEW;
}
只要state状态不是NEW,则任务已经执行完毕了,因为state状态不存在类似“任务正在执行中”这种状态,即使是短暂的中间态,也是发生在任务已经执行完毕,正在设置任务结果的时候。
接下来分析FutureTask非常重要的get方法:
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
}
首先判断FutureTask的状态是否为完成状态,如果是完成状态,说明已经执行过set或setException方法,返回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);
}
可以看到,若FutureTask的状态是Normal,即正确执行了set方法,get方法直接返回处理的结果,如果是取消状态,即执行了setException,则抛出CancellationException异常。
如果get时,FutureTask的状态为未完成状态,则调用awaitDone方法进行阻塞。awaitDone():
/**
* Awaits completion or aborts on interrupt or timeout
* 调用 awaitDone 进行线程的自旋
* 自旋一般调用步骤
* 1) 若支持线程中断, 判断当前的线程是否中断
* a. 中断, 退出自旋, 在线程队列中移除对应的节点
* b. 进行下面的步骤
* 2) 将当前的线程构造成一个 WaiterNode 节点, 加入到当前对象的队列里面 (进行 cas 操作)
* 3) 判断当前的调用是否设置阻塞超时时间
* a. 有 超时时间, 调用 LockSupport.parkNanos; 阻塞结束后, 再次进行 自旋 , 还是到同一个if, 但 nanos = 0L, 删除链表中对应的 WaiterdNode, 返回 state值
* b. 没 超时时间, 调用 LockSupport.park
*
* @param timed true if use timed waits
* @param nanos time to waits, if timed
* @return state upon completion
*/
private int awaitDone(boolean timed, long nanos) throws InterruptedException{
// default timed = false, nanos = 0, so deadline = 0
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
for(;;){
// Thread.interrupted 判断当前的线程是否中断(调用两次会清楚对应的状态位)
// Thread.interrupt 将当前的线程设置成中断状态
if(Thread.interrupted()){
removeWaiter(q, Thread.currentThread().getId());
throw new InterruptedException();
}
int s = state;
/** 1. s = NORMAL, 说明程序执行成功, 直接获取对应的 V
*/
if(s > COMPLETING){
if(q != null){
q.thread = null;
}
return s;
}
// s = COMPLETING ; 看了全部的代码说明整个任务在处理的中间状态, s紧接着会进行改变
// s 变成 NORMAL 或 EXCEPTION
// 所以调用 yield 让线程状态变更, 重新进行CPU时间片竞争, 并且进行下次循环
else if(s == COMPLETING){ // cannot time out yet
Thread.yield();
}
// 当程序调用 get 方法时, 一定会调用一次下面的方法, 对 q 进行赋值
else if(q == null){
q = new WaitNode();
}
// 判断有没将当前的线程构造成一个节点, 赋值到对象对应的属性里面
// 第一次 waiters 一定是 null 的, 进行赋值的是一个以 q 为首节点的栈
else if(!queued){
queued = unsafe.compareAndSwapObject(this, waitersOffset, q.next = waiters, q);
}
// 调用默认的 get()时, timed = false, 所以不执行这一步
else if(timed){
// 进行阻塞时间的判断, 第二次循环时, nanos = 0L, 直接 removeWaiter 返回现在 FutureTask 的 state
nanos = deadline - System.nanoTime();
if(nanos <= 0L){
removeWaiter(q, Thread.currentThread().getId());
return state;
}
LockSupport.parkNanos(this, nanos);
}
// 进行线程的阻塞
else{
LockSupport.park(this);
}
}
}
有一点我们先特别说明一下,FutureTask中会涉及到两类线程,一类是执行任务的线程,它只有一个,FutureTask的run方法就由该线程来执行;一类是获取任务执行结果的线程,它可以有多个,这些线程可以并发执行,每一个线程都是独立的,都可以调用get方法来获取任务的执行结果。如果任务还没有执行完,则这些线程就需要进入Treiber栈中挂起,直到任务执行结束,或者等待的线程自身被中断。
理清了这一点后,我们再来详细看看awaitDone方法。可以看出,该方法的大框架是一个自旋操作,我们一段一段来看:
for (;;) {
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
// ...
}
我们先检测当前线程是否被中断了,这是因为get方法是阻塞式的,如果等待的任务还没有执行完,则调用get方法的线程会被扔到Treiber栈中挂起等待,直到任务执行完毕。但是,如果任务迟迟没有执行完毕,则我们也有可能直接中断在Treiber栈中的线程,以停止等待。
当检测到线程被中断后,我们调用了removeWaiter:
private void removeWaiter(WaitNode node) {
if (node != null) {
...
}
}
removeWaiter的作用是将参数中的node从等待队列(即Treiber栈)中移除。如果此时线程还没有进入Treiber栈,则 q=null,那么removeWaiter(q)啥也不干。在这之后,我们就直接抛出了InterruptedException异常。
接着往下看:
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);
}
- 如果任务已经进入终止态(s > COMPLETING),我们就直接返回任务的状态;
- 否则,如果任务正在设置执行结果(s == COMPLETING),我们就让出当前线程的CPU资源继续等待
- 否则,就说明任务还没有执行,或者任务正在执行过程中,那么这时,如果q现在还为null, 说明当前线程还没有进入等待队列,于是我们新建了一个WaitNode, WaitNode的构造函数我们之前已经看过了,就是生成了一个记录了当前线程的节点;
- 如果q不为null,说明代表当前线程的WaitNode已经被创建出来了,则接下来如果queued=false,表示当前线程还没有入队,所以我们执行了:
queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q);
这行代码的作用是通过CAS操作将新建的q节点添加到waiters链表的头节点之前,其实就是Treiber栈的入栈操作,写的还是很简洁的,一行代码就搞定了.
这个CAS操作就是为了保证同一时刻如果有多个线程在同时入栈,则只有一个能够操作成功,也即Treiber栈的规范。
如果以上的条件都不满足,则再接下来因为现在是不带超时机制的get,timed为false,则else if代码块跳过,然后来到最后一个else, 把当前线程挂起,此时线程就处于阻塞等待的状态。
至此,在任务没有执行完毕的情况下,获取任务执行结果的线程就会在Treiber栈中被LockSupport.park(this)挂起了。
那么这个挂起的线程什么时候会被唤醒呢?有两种情况:
- 任务执行完毕了,在finishCompletion方法中会唤醒所有在Treiber栈中等待的线程
- 等待的线程自身因为被中断等原因而被唤醒。
我们接下来就继续看看线程被唤醒后的情况,此时,线程将回到for(;;)循环的开头,继续下一轮循环:
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); // 挂起的线程从这里被唤醒
}
首先自然还是检测中断,所不同的是,此时q已经不为null了,因此在有中断发生的情况下,在抛出中断之前,多了一步removeWaiter(q)操作,该操作是将当前线程从等待的Treiber栈中移除,相比入栈操作,这个出栈操作要复杂一点,这取决于节点是否位于栈顶。下面我们来仔细分析这个出栈操作:
removeWaiter
实现并发链表中移除队列节点的一个操作
private void removeWaiter(WaitNode node, long i){
logger.info("removeWaiter node" + node +", i: "+ i +" begin");
if(node != null){
node.thread = null; // 将移除的节点的thread=null, 为移除做标示
retry:
for(;;){ // restart on removeWaiter race
for(WaitNode pred = null, q = waiters, s; q != null; q = s){
logger.info("q : " + q +", i:"+i);
s = q.next;
// 通过 thread 判断当前 q 是否是需要移除的 q节点
if(q.thread != null){
pred = q;
logger.info("q : " + q +", i:"+i);
}
// 何时执行到这个if条件 ?
// hehe 只有第一步不满足时, 也就是q.thread=null (p就是应该移除的节点)
else if(pred != null){
logger.info("q : " + q +", i:"+i);
pred.next = s; // 将前一个节点的 next 指向当前节点的 next 节点
// pred.thread == null 这种情况是在多线程进行并发 removeWaiter 时产生的
// 而此时真好移除节点 node 和 pred, 所以loop跳到retry, 在进行一次
if(pred.thread == null){ // check for race
continue retry;
}
}
// 这一步何时操作呢?
// 想想 若p是头节点
else if(!unsafe.compareAndSwapObject(this, waitersOffset, q, s)){
logger.info("q : " + q +", i:"+i);
continue retry; // 这一步还是 cheak for race
}
}
break ;
}
logger.info("removeWaiter node" + node +", i: "+ i +" end");
}
}
首先,我们把要出栈的WaitNode的thread属性设置为null, 这相当于一个标记,是我们后面在waiters链表中定位该节点的依据。
(1) 要移除的节点就在栈顶
我们先来看看该节点就位于栈顶的情况,这说明在该节点入栈后,并没有别的线程再入栈了。由于一开始我们就将该节点的thread属性设为了null,因此,前面的q.thread != null 和 pred != null都不满足,我们直接进入到最后一个else if 分支:
else if (!UNSAFE.compareAndSwapObject(this, waitersOffset, q, s))
continue retry;
这一段是栈顶节点出栈的操作,和入栈类似,采用了CAS比较,将栈顶元素设置成原栈顶节点的下一个节点。
值得注意的是,当CAS操作不成功时,程序会回到retry处重来,但即使CAS操作成功了,程序依旧会遍历完整个链表,找寻node.thread == null 的节点,并将它们一并从链表中剔除。
(2) 要移除的节点不在栈顶
当要移除的节点不在栈顶时,我们会一直遍历整个链表,直到找到q.thread == null的节点,找到之后,我们将进入
else if (pred != null) {
pred.next = s;
if (pred.thread == null) // check for race
continue retry;
}
这是因为节点不在栈顶,则其必然是有前驱节点pred的,这时,我们只是简单的让前驱节点指向当前节点的下一个节点,从而将目标节点从链表中剔除。
注意,后面多加的那个if判断是很有必要的,因为removeWaiter方法并没有加锁,所以可能有多个线程在同时执行,WaitNode的两个成员变量thread和next都被设置成volatile,这保证了它们的可见性,如果我们在这时发现了pred.thread == null,那就意味着它已经被另一个线程标记了,将在另一个线程中被拿出waiters链表,而我们当前目标节点的原后继节点现在是接在这个pred节点上的,因此,如果pred已经被其他线程标记为要拿出去的节点,我们现在这个线程再继续往后遍历就没有什么意义了,所以这时就调到retry处,从头再遍历。
如果pred节点没有被其他线程标记,那我们就接着往下遍历,直到整个链表遍历完。
在该方法中,会传入一个需要移除的节点,我们会将这个节点的thread属性设置成null,以标记该节点。然后无论如何,我们会遍历整个链表,清除那些被标记的节点(只是简单的将节点从链表中剔除)。如果要清除的节点就位于栈顶,则还需要注意重新设置waiters的值,指向新的栈顶节点。所以可以看出,虽说removeWaiter方法传入了需要剔除的节点,但是事实上它可能剔除的不止是传入的节点,而是所有已经被标记了的节点,这样不仅清除操作容易了些(不需要专门去定位传入的node在哪里),而且提升了效率(可以同时清除所有已经被标记的节点)。
我们再回到awaitDone方法里:
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);
}
}
如果线程不是因为中断被唤醒,则会继续往下执行,此时会再次获取当前的state状态。所不同的是,此时q已经不为null, queued已经为true了,所以已经不需要将当前节点再入waiters栈了。
至此我们知道,除非被中断,否则get方法会在原地自旋等待(用的是Thread.yield,对应于s == COMPLETING)或者直接挂起(对应任务还没有执行完的情况),直到任务执行完成。而我们前面分析run方法和cancel方法的时候知道,在run方法结束后,或者cancel方法取消完成后,都会调用finishCompletion()来唤醒挂起的线程,使它们得以进入下一轮循环,获取任务执行结果。
最后,等awaitDone函数返回后,get方法返回了report(s),以根据任务的状态,汇报执行结果:
@SuppressWarnings("unchecked")
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);
}
可见,report方法非常简单,它根据当前state状态,返回正常执行的结果,或者抛出指定的异常。
get(long timeout, TimeUnit unit)
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方法中多了超时检测:
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos);
}
即,如果指定的超时时间到了,则直接返回,如果返回时,任务还没有进入终止状态,则直接抛出TimeoutException异常,否则就像get()方法一样,正常的返回执行结果。
小结
FutureTask实现了Runnable和Future接口,它表示了一个带有任务状态和任务结果的任务,它的各种操作都是围绕着任务的状态展开的,值得注意的是,在所有的7个任务状态中,只要不是NEW状态,就表示任务已经执行完毕或者不再执行了,并没有表示“任务正在执行中”的状态。
除了代表了任务的Callable对象、代表任务执行结果的outcome属性,FutureTask还包含了一个代表所有等待任务结束的线程的Treiber栈,这一点其实和各种锁的等待队列特别像,即如果拿不到锁,则当前线程就会被扔进等待队列中;这里则是如果任务还没有执行结束,则所有等待任务执行完毕的线程就会被扔进Treiber栈中,直到任务执行完毕了,才会被唤醒。
FutureTask虽然为我们提供了获取任务执行结果的途径,遗憾的是,在获取任务结果时,如果任务还没有执行完成,则当前线程会自旋或者挂起等待,这和我们实现异步的初衷是相违背的,我们后面将继续介绍另一个同步工具类CompletableFuture, 它解决了这个问题。
FutureTask的使用场景
FutureTask可用于异步获取执行结果或者取消执行任务的场景,通过传入Runnable或者Callable的任务给FutureTask,直接调用其run方法或者放入线程池执行,之后通过FutureTask的get方法异步获取执行结果,它非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果,并且FutureTask还可以确保即使调动了多次run方法,它都只执行一次Runnable或者Callable任务,或者通过cancel取消FuturTask的执行。
public class FutureTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
System.out.println("====进入主线程执行任务");
//通过线程池管理多线程
ExecutorService threadPool = Executors.newCachedThreadPool();
//线程池提交一个异步任务
System.out.println("====提交异步任务");
Future> future = threadPool.submit(new Callable>() {
@Override
public HashMap call() throws Exception {
System.out.println("异步任务开始执行....");
Thread.sleep(2000);
System.out.println("异步任务执行完毕,返回执行结果!!!!");
return new HashMap(){
{this.put("futureKey", "成功获取future异步任务结果");}
};
}
});
System.out.println("====提交异步任务之后,立马返回到主线程继续往下执行");
Thread.sleep(1000);
System.out.println("====此时需要获取上面异步任务的执行结果");
boolean flag = true;
while(flag){
//异步任务完成并且未被取消,则获取返回的结果
if(future.isDone() && !future.isCancelled()){
HashMap futureResult = future.get();
System.out.println("====异步任务返回的结果是:"+futureResult.get("futureKey"));
flag = false;
}
}
//关闭线程池
if(!threadPool.isShutdown()){
threadPool.shutdown();
}
}
}
====进入主线程执行任务
====提交异步任务
====提交异步任务之后,立马返回到主线程继续往下执行
异步任务开始执行....
====此时需要获取上面异步任务的执行结果
异步任务执行完毕,返回执行结果!!!!
====异步任务返回的结果是:成功获取future异步任务结果