Future接口
Future接口被设计用来代表一个异步操作的执行结果。你可以用它来获取一个操作的执行结果,取消一个操作,判断一个操作是否已经完成或者是否被取消
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
boolean isDone();
Future接口一共定义了五个方法:
- get()
该方法用来获取执行结果,如果任务还在执行中,就阻塞等待; - get(long timeout,TimeUnit unit)
该方法同get方法类似,所不同的是,它最多等待指定的时间。如果指定的时间内任务没有完成,则会抛出TimeoutException异常; - cancel(boolean mayInterruptIfRunnintg)
该方法用来尝试取消一个任务的执行,它的返回值是boolean类型,表示取消操作是否成功。 - isCancelled()
该方法用来判断任务是否被取消了。如果一个任务在正常执行完成之前被cancel掉了,则返回true - isDone()
如果一个任务已经结束,则返回true。注意,这里的任务结束包含了以下三种情况:任务正常执行完毕,任务抛出了异常,任务已经被取消。
关于cancel 方法,这里要补充说几点:
首先有以下三种情况之一的,cancel操作一定是失败的:
1.任务已经执行完成了
2.任务已经被取消了
3.任务因为某种原因不能被取消
其他情况下,cancel操作将返回true。值得注意的是,cancel操作返回true并不代表任务真的就是被取消了,这取决于发送cancel状态时任务所处的状态:
1.如果发起cancel时任务还没有正式开始运行,则随后任务就不会被执行
2.如果发起cancel时任务已经在运行了,则这时就需要看mayInterruptIfRunning参数了:
- 如果mayInterruptIfRunning为true,则当前在执行的任务会被中断
- 如果mayInterruptIfRunning为false,则可以允许正在执行的任务继续运行,直到它执行完毕
RunnableFuture接口
RunnableFuture接口人如其名,就是同时实现了Runnable接口和Future接口;
public interface RunnableFuture extends Runnable, Future {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
有的同学可能会对这个接口产生疑惑,既然已经继承了Runnable,该接口自然就继承了run方法,为什么要在该接口的内部再写一个run方法了?
单纯从理论上来说,这里确实是没有必要的,再多写一遍,我觉得大概就是为了看上去直观一点,便于文档或者UML图展示。
在使用ThreadPoolExecutor的submit提交任务交给线程池中的线程去执行。有几个submit方法
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public Future> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public Future submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public Future submit(Callable task) {
if (task == null) throw new NullPointerException();
RunnableFuture ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
在submit方法中,我们看见有一行代码:
RunnableFuture ftask = newTaskFor(task);
这行代码的功能:对我们的task进行了类型转行,task类型是Runnable/Callable,转化为了一个RunnableFuture对象。
根据task类型由于有两种Runnable/Callable,分别有两种不同的重载方法newTaskFor。如下:
/**
* Returns a {@code RunnableFuture} for the given runnable and default
* value.
*
* @param runnable the runnable task being wrapped
* @param value the default value for the returned future
* @param the type of the given value
* @return a {@code RunnableFuture} which, when run, will run the
* underlying runnable and which, as a {@code Future}, will yield
* the given value as its result and provide for cancellation of
* the underlying task
* @since 1.6
*/
protected RunnableFuture newTaskFor(Runnable runnable, T value) {
return new FutureTask(runnable, value);
}
/**
* Returns a {@code RunnableFuture} for the given callable task.
*
* @param callable the callable task being wrapped
* @param the type of the callable's result
* @return a {@code RunnableFuture} which, when run, will call the
* underlying callable and which, as a {@code Future}, will yield
* the callable's result as its result and provide for
* cancellation of the underlying task
* @since 1.6
*/
protected RunnableFuture newTaskFor(Callable callable) {
return new FutureTask(callable);
}
从newTaskFor函数中可以看到,就是直接调用了FutureTask的有参构造函数。
FutureTask是继承了RunnableFuture类来实现的。如下:
public class FutureTask implements RunnableFuture
下面来看下RunnableFuture类的内容,如下:
/**
* A {@link Future} that is {@link Runnable}. Successful execution of
* the {@code run} method causes completion of the {@code Future}
* and allows access to its results.
* @see FutureTask
* @see Executor
* @since 1.6
* @author Doug Lea
* @param The result type returned by this Future's {@code get} method
*/
//作为Runnable的Future。成功执行run方法可以完成Future并允许访问其结果
public interface RunnableFuture extends Runnable, Future {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
RunnableFuture接口比较简单,继承了Runnable,Future接口。并只有一个run方法。
下面我们下看下FutureTask类的构造方法内部实现:
/**
* Creates a {@code FutureTask} that will, upon running, execute the
* given {@code Callable}.
*
* @param callable the callable task
* @throws NullPointerException if the callable is null
*/
public FutureTask(Callable callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
/**
* Creates a {@code FutureTask} that will, upon running, execute the
* given {@code Runnable}, and arrange that {@code get} will return the
* given result on successful completion.
*
* @param runnable the runnable task
* @param result the result to return on successful completion. If
* you don't need a particular result, consider using
* constructions of the form:
* {@code Future> f = new FutureTask(runnable, null)}
* @throws NullPointerException if the runnable is null
*/
//创建一个FutureTask对象,执行的还是里面所包含的Runnable对象 如果Runnable对象正常执行完成,则FutureTask对象调用get方法的时候就会得到结果result
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
在第二个构造函数中,我们看到了
this.callable=Executors.callable(runable,result);
这行代码是将Runnable类型转换为了Callable类型。因此,我们可以看下Executors.callable(runnable,result)方法的实现,到底是如何转行的呢?
public static Callable callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter(task, result);
}
将Runnable适配为了一个Callable对象,转化为的对象虽然是Callable对象了,但是调用此对象的call方法其实就是调用了Runnable接口的run方法并且返回值是null。
看下RunnableAdapter类,此类实现了Callable接口.
/**
* A callable that runs given task and returns given result.
*/
private static final class RunnableAdapter implements Callable {
private final Runnable task;
private final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}
可以明显看出来,这个方法采用了设计模式种的适配器模式,将一个Runnable类型对象适配成Callable类型。
因为Runnable接口没有返回值,所以为了与Callable兼容,我们额外传入了一个result参数,使得返回的Callable对象的call方法直接执行Runnable的run方法,然后返回传入的result参数。
有些人可能会有这个疑问,你把result参数传进去,又原封不动的返回出来,有什么意义呀?这样做确实没有什么意义,result参数的存在只是为了将一个Runnable对象适配成Callable类型。
总结一下上面的逻辑:
- 首先,在我们写程序的时候,我们可能在线程池中的submit方法来提交一个任务task,这个task可能是runnable对象,也可能是callable对象。为了方便处理,我们需要将Runnable,Callable统一起来,因此借助了Executors类中的RunnableAdapter类将Runnable对象适配为了一个Callable对象。
- 而submit方法要求返回一个Future对象,我们可以通过这个对象来获取任务的运行结果。而FutureTask作为Future的实现类实现了对象任务task的封装,并且可以通过封装后的对象获取返回值。
FutureTask
public class FutureTask implements RunnableFuture
public interface RunnableFuture extends Runnable, Future
FutureTask,人如其名,FutureTask包含了Future和Task两部分。
FutureTask实现了RunnableFuture接口,即Runnable接口和Future接口,其中Runnable接口对应了FutureTask名字种的Task
,代表FutureTask本质上也是一个任务。而Future接口就对应了FutureTask名字种的Future,表示了我们对于这个任务可以执行某些操作,列如:判断任务是否执行完毕,获取任务的执行结果,取消执行的任务等。
所以简单来说,FutureTask本质上就是一个"Task",我们可以把它当作简单的Runnable对象来使用。但是它又同时实现了Future接口,因此我们可以对它所代表的"Task"进行额外的控制操作。
Java并发工具类的三板斧
状态
队列
CAS
状态
首先是找状态,在FutureTask中,状态是由state属性来表示,主要有:
1.NEW(开始状态)
2.COMPLETING(正在运行状态)
3.NORMAL(正常运行完结束状态)
4.EXCEPTIONAL(异常状态)
5.CANCELLED(取消状态)
6.INTERRUPTING(中断)
7.INTERRUPTED(中断结束状态)
源码中对这几个状态以及状态之间的转换关系如下所示:
/**
* 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
*/
任务的初始状态都是
NEW
,这一点是构造函数保证的。任务的终止状态有4种:
-NORMAL
:任务正常执行完毕
-EXCEPTIONAL
:任务执行过程中发生异常
-CANCELLED
:任务被取消
-INTERRUPTED
:任务被中断-
任务的中间状态有两种:
-
COMPLETING
正在设置任务结果 -
INTERRUPTING
正在中断运行任务的线程
值得一提的是,任务的中间状态是一个瞬时的,它非常短暂,而且任务的中间态并不代表任务正在执行,而是任务已经执行完了,正在设置最终的返回结果,
,所以可以这么说:
-
只要state 不处于
NEW
状态,就说明任务已经执行完毕了。
注意,这里的执行完毕就是指传入的Callable对象的call方法执行完毕,或者抛出异常,所以这里的COMPLETING
的名字显得有点迷惑,它并不意味着任务正在执行中,而意味着call方法执行完毕,正在设置任务执行的结果。
而将一个任务的状态设置为终止态只有三种方法:
- set
- setException
- cancel
队列
接着我们来看队列,在FutureTask中,队列的实现是一个单向的链表,它表示所有等待任务执行完毕的线程的集合。
我们知道,FutureTask实现了Future接口,可以获取"Task"的执行结果,那么如果获取结果时,任务还没有执行完毕怎么办呢?那么获取结果的线程就会在一个等待队列中挂起,直到任务执行完毕被唤醒。这一点有点类似我们之前学习的AQS中的sync queue
。
我们之前说过,在并发编程中使用队列通常是将当前线程包装成某种类型的数据结构仍到等待队列中,我们先来看看队列中的每一个节点是怎样的结构:
static final class WaitNode {
volatile Thread thread;
volatile WaitNode next;
WaitNode() { thread = Thread.currentThread(); }
}
可见,相比于AQS的sync queue
所使用的双向链表中的Node,这个WaitNode要简单多了,它只包含了一个记录线程的thread
属性和指向下一个节点的next
属性。FutureTask中的这个单向链表是当做栈
来使用的。它是一个线程安全的栈,为啥要使用一个线程安全的栈呢?因为同一时刻可能有多个线程都在获取任务的执行结果,如果任务还在执行过程中,则这些线程就要被包装成WaitNode扔到栈顶,即完成入栈操作,这样就有可能会出行多个线程同时入栈的情况,因此需要使用CAS操作保证入栈的线程安全,对于出栈也是同理。
由于FutureTask中的队列本质上是一个栈,那么使用这个队列就只需要一个指向栈节点的指针就行了,在FutureTask中,就是waiters
属性;
/** Treiber stack of waiting threads */
private volatile WaitNode waiters;
事实上,它就是整个单向链表的头节点,
综上,FutureTask中所使用的队列结构如下:
CAS操作
CAS操作大多数是用来改变状态的,在FutureTask中也不例外。我们一般在静态代码块中初始化需要CAS操作的属性和偏移量
// Unsafe mechanics
private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
private static final long STATE;
private static final long RUNNER;
private static final long WAITERS;
static {
try {
STATE = U.objectFieldOffset
(FutureTask.class.getDeclaredField("state"));
RUNNER = U.objectFieldOffset
(FutureTask.class.getDeclaredField("runner"));
WAITERS = U.objectFieldOffset
(FutureTask.class.getDeclaredField("waiters"));
} catch (ReflectiveOperationException e) {
throw new Error(e);
}
// Reduce the risk of rare disastrous classloading in first call to
// LockSupport.park: https://bugs.openjdk.java.net/browse/JDK-8074773
Class> ensureLoaded = LockSupport.class;
}
从这个静态代码块中我们也可以看出,CAS操作主要针对3个属性,包括state
,runner
,和waiters
,说明这3个属性基本是会被多个线程同时访问的。其中state
属性代表了任务的状态,waiters
属性代表了指向栈顶节点的指针,这两个我们上面分析了。runner
属性代表了执行FutureTask中的"Task"的线程。为什么需要一个属性来记录执行任务的线程呢?这是为了中断或者取消任务做准备的,只有知道了执行任务的线程是谁,我们才能区中断它。
FutureTask类run方法
public void run() {
if (state != NEW ||
!U.compareAndSwapObject(this, RUNNER, 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);
}
}
/**
* 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
// assert state == INTERRUPTED;
// We want to clear any interrupt we may have received from
// cancel(true). However, it is permissible to use interrupts
// as an independent mechanism for a task to communicate with
// its caller, and there is no way to clear only the
// cancellation interrupt.
//
// Thread.interrupted();
}
首先我们看到,在run方法的一开始,就检查当前状态是不是NEW,并且使用CAS操作将runner
属性设置为当前线程,即记录执行任务的线程。可见,runner
属性是在运行时被初始化的。
接下来,我们就调用了Callable对象的call方法来执行任务,如果任务执行成功,就使用set(result)
设置结果,否则,用setException(ex)
设置抛出的异常。
我们先来看看set(result)
方法:
/**
* Sets the result of this future to the given value unless
* this future has already been set or has been cancelled.
*
* This method is invoked internally by the {@link #run} method
* upon successful completion of the computation.
*
* @param v the value
*/
protected void set(V v) {
if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {
outcome = v;
U.putOrderedInt(this, STATE, NORMAL); // final state
finishCompletion();
}
}
这个方法一开始通过CAS操作将state
属性由原理的NEW
状态修改为COMPLETING
状态,我们一开始介绍state状态的时候说过,COMPLETING
是一个非常短暂的中间态,表示正在设置执行的结果。
状态设置成功后,我们就把任务执行结果赋值给outcome,然后直接把state状态设置为NORMAL
,注意,这里是直接设置,没有先比较再设置的操作,由于state属性被设置成volatile,保证了state状态对其他线程的可见性。在这之后,我们调用了finishCompletion
来完成执行结果的设置。
接下来我们再来看看发生了异常的版本setException(ex)
;
protected void setException(Throwable t) {
if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {
outcome = t;
U.putOrderedInt(this, STATE, EXCEPTIONAL); // final state
finishCompletion();
}
}
可见,除了将outcome属性赋值为异常对象,以及将state的终止状态修改为EXCEPTIONAL
,其余都和set方法类似。在方法的最后,都调用了finishCompletion()
来完成执行结果的设置。那么我们就来看看finishCompletion()
干了点啥:
/**
* Removes and signals all waiting threads, invokes done(), and
* nulls out callable.
*/
private void finishCompletion() {
// assert state > COMPLETING;
for (WaitNode q; (q = waiters) != null;) {
if (U.compareAndSwapObject(this, WAITERS, 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操作:
U.compareAndSwapObject(this, WAITERS, q, null)
该方法是将waiters
属性的值由原值设置为null,我们知道,waiters
属性指向了栈的栈顶节点,可以说是代表了整个栈,将该值设为null的目的就是清空整个栈。如果设置不成功,则if语句块不会被指向,又进行下一轮for循环,而下一轮for循环的判断条件又是waiters!=null
,由此我们知道,虽然最外层的for循环乍一看好像是什么遍历节点的操作,其实只是为了确保waiters属性被成功设置为null,本质上相当于一个自旋操作。
将waiters属性设置为null以后,接下来for(;;)
死循环才是真正的遍历节点,可以看出,循环内部就是一个普通的遍历链表的操作,这个循环的作用也正式遍历链表中所有等待的线程,并唤醒他们。
最后我们来看看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
中间态。
run方法重点做了以下几件事:
1.将runner属性设置为当前正在执行run方法的线程
2.调用callable成员变量的call方法来执行任务
3.设置执行结果outcome,如果执行成功,则outcome保存的就是执行结果,如果执行过程中发生了异常,则outcome中保存的就是异常,在设置结果之前,先将state状态设为中间态
4.对outcome的赋值完成后,设置state状态为终止态(NORAML
或者EXCEPTIONAL
)
5.唤醒Treiber栈中所有等待的线程
6.善后清理(waiters,callable,runner设为null)
7,检查是否有遗漏的中断,如果有,等待中断状态完成。
这里再说一句,我们前面说的state只要不是NEW状态,就说明任务已经执行完成了
就体现再这里,因为run方法中,我们是在c.call()
执行完毕后或者抛出异常之后才开始设置中间态和终止态的。
cancel(boolean mayInterruptIfRunning)
public boolean cancel(boolean mayInterruptIfRunning) {
if (!(state == NEW &&
U.compareAndSwapInt(this, STATE, 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
U.putOrderedInt(this, STATE, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
}
首先,对于任务已经执行完成了或者任务已经被取消过了,则cancel操作一定是失败的(返回false),这两条,是通过简单判断state值是否为NEW
实现的,因为我们前面说过,只要state不为NEW,说明任务已经执行完毕了。从代码中也可以看出,只要state部位NEW,则直接返回false。
如果state还是NEW状态,我们再往下看:
U.compareAndSwapInt(this, STATE, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)
这一段根据mayInterruptIfRunning
的值将state的状态由 NEW
设置为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
U.putOrderedInt(this, STATE, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
我们知道,runner
属性中存放的是当前正在执行任务的线程,因此,这个try快的目的就是中断当前正在执行任务的线程,最后将state的状态设置为INTERRUPTED
,当然,中断操作完成后,还需要通过finishCompleted()
来唤醒所有再栈中等待的线程。
我们现在总结一下,cancel方法实际上完成以下两种状态转换之一:
1.NEW-> CANCELLED
(对应于 mayInterruptIfRunning=false)
2.NEW->INTERRUPTING->INTERRUPTED
(对应于mayInterruptIfRunning=true)
对于第一条路径,虽说cancel方法最终返回了true,但它只是简单的把state状态设为CANCELLED,并不会中断线程的执行。但是这样带来的后果是,任务即使执行完毕了,也无法设置任务的执行结果,因为前面分析run方法的时候我们知道,设置任务结果有一个中间状态,而这个中间态的设置,是以当前state状态为NEW为前提的。
对于第二条路径,则会中断执行任务的线程。但是响不响应这个中断是由执行任务的线程自己决定的,更具体的说,这取决于c.call()
方法内部是否对中断进行了响应,是否将中断异常抛出。
那call方法中是怎么处理中断的呢?从上面的代码中可以看出,catch语句处理了所有的Throwable
的异常,这自然也包括了中断异常。
然而,值得一提的是,即使这里进入了catch(Throwable ex){}
代码块,setException(ex)
的操作一定是失败的,因为再我们取消任务执行的线程中,我们已经先把state状态设为了INTERRUPTING
了,而setException(ex)
的操作要求设置前线程的状态为NEW
。所以这里响应cancel方法方法所造成的中断最大的意义不是为了对中断进行处理,而是简单的停止任务线程的执行,节省CPU资源。
那么有人要问了,既然这个setException(ex)
的操作一定是失败的,那放在这里有什么用呢?事实上,这个setException(ex)
是用来处理任务自己再正常执行过程中产生的异常的,在我们没有主动去cancel任务时,任务的state状态在执行过程中就回始终时NEW
,如果任务此时自己发生了异常,则这个异常就会被setException(ex)
方法成功的记录到outcome
中。
反正无论如何,run方法最终都会进入finally块,而这时候它会发现s>=INTERRUPTING
,如果检测发现s=INTERRUPTING
,说明cancel方法还没有执行到中断当前线程的地方,那就等待它将state状态设置成INTERUPTED
。到这里,对cancel方法的分析就和上面对run方法的分析对接上了。
isCancelled()
说完了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方法介绍
当我们使用如下代码的时候:
Future f=pool.submit(new Runnable(){});
....
Object obj=f.get();//获取任务的返回结果
Future中的get方法获取结果时里面的内部实现时怎么样的呢?我们一起来看看:
/**
* @throws CancellationException {@inheritDoc}
*/
public V get() throws InterruptedException, ExecutionException {
int s = state;
//说明任务还正在执行,需要等待
//返回值为任务执行后的状态值,可能时正常执行完,也可能时中断抛出异常返回,也可能是超时返回
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
/**
* @throws CancellationException {@inheritDoc}
*/
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);
}
/**
* Awaits completion or aborts on interrupt or timeout.
*
* @param timed true if use timed waits
* @param nanos time to wait, if timed
* @return state upon completion or at timeout
*/
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
// The code below is very delicate, to achieve these goals:
// - call nanoTime exactly once for each call to park
// - if nanos <= 0L, return promptly without allocation or nanoTime
// - if nanos == Long.MIN_VALUE, don't underflow
// - if nanos == Long.MAX_VALUE, and nanoTime is non-monotonic
// and we suffer a spurious wakeup, we will do no worse than
// to park-spin for a while
long startTime = 0L; // Special value 0L means not yet parked
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) //正在执行
// We may have already promised (via isDone) that we are done
// so never return empty-handed or throw InterruptedException
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 = U.compareAndSwapObject(this, WAITERS,
q.next = waiters, q);
else if (timed) {//检查等待是否超时了,如果超时,则返回此时的状态,否则继续等待挂起
final long parkNanos;
if (startTime == 0L) { // first time
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;
}
// nanoTime may be slow; recheck before parking
if (state < COMPLETING)
LockSupport.parkNanos(this, parkNanos);
}
else
LockSupport.park(this);//上面条件如果都不满足,则唤醒该线程
}
}
/**
* Returns result or throws exception for completed task.
*
* @param s completed state value
*/
//如果正常执行完,则返回结果,否则根据任务的状态抛出相应的异常
@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);
}
有一点我们先特别说明一下,FutureTask中会涉及到两类线程,一类是执行任务的线程,它只有一个,FutureTask的run方法就由该线程来执行,一类是获取任务执行结果的线程,它可以有多个,这些线程可以并发执行,每一个线程都是独立的,都可以调用get方法来获取任务的执行结果。
如果任务还没有执行完,则这些线程就需要进入栈中挂起,直到任务执行结束,或者等待的线程自身被中断。
...............
for (;;) {
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
//.................
}
我们先检测当前线程是否被中断了,这是因为get方法是阻塞式的,如果等待的任务还没有执行完,则调用get方法的线程会被仍到栈中挂起等待,直到任务执行完毕。但是,如果任务迟迟没有执行完毕,则我们也有可能直接中断在栈中的线程,以停止等待。
当检测到线程被中断后,我们调用了removeWaiter:
- 如果任务已经进入终止态(
S>COMPLETING
),我们就直接返回任务的状态; - 否则,如果任务正在设置执行结果(
s==COMPLETING
),我们就让出当前线程的CPU资源继续等待 - 否则,就说明任务还没有执行,或者任务正在执行过程中,那么这时,如果q现在还为null,说明当前线程还没有进入等待队列,于是我们新建了一个
WaitNode
,WaitNode
的构造函数我们之前已经看过了,就是生成一个记录了当前线程的节点; - 如果q不为null,说明代表当前线程的WaitNode已经被创建出来了,则接下来如果
queued=false
,表示当前线程还没有入队,所以我们执行入队操作。 - 如果以上的条件都不满足,则接下来因为现在是不带超时机制的get,timed为false,则
else if
代码块跳过,然后来到最后一个else,把当前线程挂起,此时线程就处于阻塞等待的状态。
至此,在任务没有执行完毕的情况下,获取任务执行结果的线程就会在栈中被LockSupport.park(this)
挂起。
那么这个挂起的线程什么时候会被唤醒呢?有两种情况:
1.任务执行完毕了,在finishCompletion
方法中会唤醒所有的在栈中等待的线程。
2.等待的线程自身因为被中断等原因还被唤醒。
至此我们知道,除非被中断, 否则get方法会在原地自旋等待或者直接挂起(对应任务还没有执行完的情况),知道任务执行完成。前面分析run方法和cancel方法的时候知道,在run方法结束后,或者cancel方法取消完成后,都会调用finishCompletion()
来唤醒挂起的线程,使他们得以进入下一轮循环,获取任务执行结果。
最后,等awaitDone函数返回后,get方法返回了report(s)
,根据任务的状态,汇报执行结果:
/**
* Returns result or throws exception for completed task.
*
* @param s completed state value
*/
@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状态,返回正常执行的结果,或者抛出指定的异常。
值得注意的是,awiteDone
方法和get方法都没有加锁,这在多个线程同时执行get方法的时候会不会产生线程安全问题呢?通过查看方法内部的参数我们知道,整个方法内部用的大多数是局部变量,因此不会产生线程安全问题,对于全局的共享变量waiters
的修改时,也使用了CAS操作,保证了线程安全,而state变量本身时volatile的,保证了读取时的可见性,因此整个方法调用虽然没有加锁,它仍然时线程安全 的。
总结
Runnable对象可以通过RunnableAdapter适配器适配到Callable对象
Future对象可以通过get方法获取任务的返回值
FutureTask可以简单来看是对任务Runnable/Callable的封装。
FutureTask实现了Runnable和Future接口,它表示了一个带有任务状态和任务结果的任务,它的各种操作都是围绕着任务的状态展开的,值得注意的是,在所有7个任务状态中,只要不是NEW
状态,就表示任务已经执行完毕或者不再执行了,并没有表示任务正在执行中的状态。
除了代表了任务的Callable对象,代表任务执行结果的outcome属性,FutureTask还包含了一个代表所有等待任务结束的线程的Treiber栈,这一点其实和各种锁的等待队列特别像,即如果拿不到锁,则当前线程就会被仍进等待队列中;这里则是如果任务还没有执行结束,则所有等待任务执行完毕的线程就会被放入栈中,直到任务执行完毕了,才会被唤醒。
FutureTask虽然为我们提供了获取任务执行的结果,遗憾的是,在获取任务结果时,如果任务还没有执行完成,则当前线程会自旋或者挂起等待,这和我们实现异步的初衷时相违背的。 我们后面将继续介绍另外一个同步工具类CompletableFuture。