API:
单独使用Runnable时:
无法获得返回值
单独使用Callable时:
无法在新线程中(new Thread(Runnable r))使用,只能使用ExecutorService
Thread类只支持Runnable
FutureTask:
实现了Runnable和Future,所以兼顾两者优点
既可以使用ExecutorService,也可以使用Thread
=================================================================
public interface Future
Future有个get方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常。
Future 主要定义了5个方法:
1)boolean cancel(boolean mayInterruptIfRunning):试图取消对此任务的执行。如果任务已完成、或已取消,或者由于某些其他原因而无法取消,则此尝试将失败。当调用 cancel 时,如果调用成功,而此任务尚未启动,则此任务将永不运行。如果任务已经启动,则 mayInterruptIfRunning 参数确定是否应该以试图停止任务的方式来中断执行此任务的线程。此方法返回后,对 isDone() 的后续调用将始终返回 true。如果此方法返回 true,则对 isCancelled() 的后续调用将始终返回 true。
2)boolean isCancelled():如果在任务正常完成前将其取消,则返回 true。
3)boolean isDone():如果任务已完成,则返回 true。 可能由于正常终止、异常或取消而完成,在所有这些情况中,此方法都将返回 true。
4)V get()throws InterruptedException,ExecutionException:如有必要,等待计算完成,然后获取其结果。
5)V get(long timeout,TimeUnit unit) throws InterruptedException,ExecutionException,TimeoutException:如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。
6)finishCompletion()该方法在任务完成(包括异常完成、取消)后调用。删除所有正在get获取等待的节点且唤醒节点的线程。和调用done方法和置空callable.
CompletionService接口能拿到按完成顺序拿到一组线程池中所有线程,依靠的就是FutureTask中的finishCompletion()方法。
FutureTask是为了弥补Thread的不足而设计的,它可以让程序员准确地知道线程什么时候执行完成并获得到线程执行完成后返回的结果(如果有需要)。
FutureTask是一种可以取消的异步的计算任务。它的计算是通过Callable实现的,它等价于可以携带结果的Runnable,并且有三个状态:等待、运行和完成。完成包括所有计算以任意的方式结束,包括正常结束、取消和异常。
Executor框架利用FutureTask来完成异步任务,并可以用来进行任何潜在的耗时的计算。一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
JDK:
此类提供了对 Future 的基本实现。仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,就不能再重新开始或取消计算。
可使用 FutureTask 包装 Callable 或 Runnable 对象。因为 FutureTask 实现了 Runnable,所以可将 FutureTask 提交给 Executor 执行。
Example1:
下面的例子模拟一个会计算账的过程,主线程已经获得其他帐户的总额了,为了不让主线程等待 PrivateAccount类的计算结果的返回而启用新的线程去处理, 并使用 FutureTask对象来监控,这样,主线程还可以继续做其他事情, 最后需要计算总额的时候再尝试去获得privateAccount 的信息。
运行结果
下面我们介绍一下FutureTask内部的一些实现机制。下文从以下几点叙述:
首先我们看一下FutureTask的继承结构:
FutureTask实现了RunnableFuture接口,而RunnableFuture继承了Runnable和Future,也就是说FutureTask既是Runnable,也是Future。
FutureTask内部定义了以下变量,以及它们的含义如下
1
2
3
4
5
6
7
|
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
;
//任务线程已中断
|
后三个字段是配合Unsafe类做CAS操作使用的。
FutureTask中使用state表示任务状态,state值变更的由CAS操作保证原子性。
FutureTask对象初始化时,在构造器中把state置为为NEW,之后状态的变更依据具体执行情况来定。
例如任务执行正常结束前,state会被设置成COMPLETING,代表任务即将完成,接下来很快就会被设置为NARMAL或者EXCEPTIONAL,这取决于调用Runnable中的call()方法是否抛出了异常。有异常则后者,反之前者。
任务提交后、任务结束前取消任务,那么有可能变为CANCELLED或者INTERRUPTED。在调用cancel方法时,如果传入false表示不中断线程,state会被置为CANCELLED,反之state先被变为INTERRUPTING,后变为INTERRUPTED。
总结下,FutureTask的状态流转过程,可以出现以下四种情况:
1. 任务正常执行并返回。 NEW -> COMPLETING -> NORMAL
2. 执行中出现异常。NEW -> COMPLETING -> EXCEPTIONAL
3. 任务执行过程中被取消,并且不响应中断。NEW -> CANCELLED
4. 任务执行过程中被取消,并且响应中断。 NEW -> INTERRUPTING -> INTERRUPTED
接下来我们一起扒一扒FutureTask的源码。我们先看一下任务线程是怎么执行的。当任务被提交到线程池后,会执行futureTask的run()方法。
1 public void run()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
public
void
run() {
// 校验任务状态
if
(state != NEW || !UNSAFE.compareAndSwapObject(
this
, runnerOffset,
null
, Thread.currentThread()))
return
;
try
{
Callable
// double check
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
runner =
null
;
int
s = state;
if
(s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
|
翻译一下,这个方法经历了以下几步
我们继续往下看,setException(Throwable t)和set(V v) 具体是怎么做的
1
2
3
4
5
6
7
8
9
10
|
protected
void
set(V v) {
// state状态 NEW->COMPLETING
if
(UNSAFE.compareAndSwapInt(
this
, stateOffset, NEW, COMPLETING)) {
outcome = v;
// COMPLETING -> NORMAL 到达稳定状态
UNSAFE.putOrderedInt(
this
, stateOffset, NORMAL);
// 一些结束工作
finishCompletion();
}
}
|
1
2
3
4
5
6
7
8
9
10
|
protected
void
setException(Throwable t) {
// state状态 NEW->COMPLETING
if
(UNSAFE.compareAndSwapInt(
this
, stateOffset, NEW, COMPLETING)) {
outcome = t;
// COMPLETING -> EXCEPTIONAL 到达稳定状态
UNSAFE.putOrderedInt(
this
, stateOffset, EXCEPTIONAL);
// 一些结束工作
finishCompletion();
}
}
|
code中的注释已经写的很清楚,故不翻译了。状态变更的原子性由unsafe对象提供的CAS操作保证。FutureTask的outcome变量存储执行结果或者异常对象,会由主线程返回。
2 get()和get(long timeout, TimeUnit unit)
任务由线程池提供的线程执行,那么这时候主线程则会阻塞,直到任务线程唤醒它们。我们通过get(long timeout, TimeUnit unit)方法看看是怎么做的
1
2
3
4
5
6
7
8
9
10
|
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的源码很简洁,首先校验参数,然后根据state状态判断是否超时,如果超时则异常,不超时则调用report(s)去获取最终结果。
当 s<= COMPLETING时,表明任务仍然在执行且没有被取消。如果它为true,那么走到awaitDone方法。
awaitDone是futureTask实现阻塞的关键方法,我们重点关注一下它的实现原理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
/**
* 等待任务执行完毕,如果任务取消或者超时则停止
* @param timed 为true表示设置超时时间
* @param nanos 超时时间
* @return 任务完成时的状态
* @throws InterruptedException
*/
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)
// 可能任务线程被阻塞了,主线程让出CPU
Thread.yield();
else
if
(q ==
null
)
// 等待线程节点为空,则初始化新节点并关联当前线程
q =
new
WaitNode();
else
if
(!queued)
// 等待线程入队列,成功则queued=true
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
// timed=false时会走到这里,挂起当前线程
LockSupport.park(
this
);
}
}
|
注释里也很清楚的写明了每一步的作用,我们以设置超时时间为例,总结一下过程
当线程被挂起之后,如果任务线程执行完毕,就会唤醒等待线程哦。这一步就是在finishCompletion里面做的,前面已经提到这个方法。我们再看看这个方法具体做了哪些事吧~
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
/**
* 移除并唤醒所有等待线程,执行done,置空callable
* nulls out callable.
*/
private
void
finishCompletion() {
//遍历等待节点
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
;
// unlink to help gc
q.next =
null
;
q = next;
}
break
;
}
}
//模板方法,可以被覆盖
done();
//清空callable
callable =
null
;
}
|
由代码和注释可以看出来,这个方法的作用主要在于唤醒等待线程。由前文可知,当任务正常结束或者异常时,都会调用finishCompletion去唤醒等待线程。这个时候,等待线程就可以醒来,开开心心的获得结果啦。
最后我们看一下任务取消
3 public boolean cancel(boolean mayInterruptIfRunning)
注意,取消操作不一定会起作用,这里我们先贴个demo
1 public class FutureDemo { 2 public static void main(String[] args) { 3 ThreadPoolExecutor executorService = (ThreadPoolExecutor) Executors.newFixedThreadPool(1); 4 // 预创建线程 5 executorService.prestartCoreThread(); 6 7 Future future = executorService.submit(new Callable
我们多次测试后发现,出现了2种打印结果,如图
结果1
结果2
第一种是任务压根没取消,第二种则是任务压根没提交成功。
方法签名注释告诉我们,取消操作是可能会失败的,如果当前任务已经结束或者已经取消,则当前取消操作会失败。如果任务尚未开始,那么任务不会被执行。这就解释了出现上图结果2的情况。我们还是从源码去分析cancel()究竟做了哪些事。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public
boolean
cancel(
boolean
mayInterruptIfRunning) {
if
(state != NEW)
return
false
;
if
(mayInterruptIfRunning) {
if
(!UNSAFE.compareAndSwapInt(
this
, stateOffset, NEW, INTERRUPTING))
return
false
;
Thread t = runner;
if
(t !=
null
)
t.interrupt();
UNSAFE.putOrderedInt(
this
, stateOffset, INTERRUPTED);
// final state
}
else
if
(!UNSAFE.compareAndSwapInt(
this
, stateOffset, NEW, CANCELLED))
return
false
;
finishCompletion();
return
true
;
}
|
执行逻辑如下
可见,cancel()方法改变了futureTask的状态位,如果传入的是false并且业务逻辑已经开始执行,当前任务是不会被终止的,而是会继续执行,直到异常或者执行完毕。如果传入的是true,会调用当前线程的interrupt()方法,把中断标志位设为true。
事实上,除非线程自己停止自己的任务,或者退出JVM,是没有其他方法完全终止一个线程的任务的。mayInterruptIfRunning=true,通过希望当前线程可以响应中断的方式来结束任务。当任务被取消后,会被封装为CancellationException抛出。
总结一下,futureTask中的任务状态由变量state表示,任务状态都基于state判断。而futureTask的阻塞则是通过自旋+挂起线程实现。理解FutureTask的内部实现机制,我们使用Future时才能更加得心应手。文中掺杂着笔者的个人理解,如果有不正之处,还望读者多多指正