文章目录
前言
一般我们创建线程的方式主要有两种,一种是直接继承Thread类,一种是实现Runnable接口。但是这两种方法是有缺陷的,就是无法任务执行完毕后获取执行的结果。怎么办呢? 当然JDK有提供相关的接口,那么就是Callable和Future接口,通过它们就可以实现在任务执行完毕后获取任务的结果。
一、Callable接口
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
Callable接口是一个泛型接口,我们通过调用call()
函数就可以执行我们的业务逻辑,而泛型V便是调用call()
函数所返回的值。其实和Runnable是很相似的,只是Callable可以返回值或者抛出异常。
二、Future接口
Future接口代表异步计算的结果,通过Future接口提供的方法我们实现查看异步计算是否执行完毕,或者等待执行完毕并获取执行结果等操作。
我们来看一下Future接口的实现:
public interface Future<V> {
/*试图取消对该任务的执行*/
boolean cancel(boolean mayInterruptIfRunning);
/*判断任务是否被取消,如果在任务正常完成之前被取消,则返回true*/
boolean isCancelled();
/*判断任务是否已经完成*/
boolean isDone();
/*如果有必要,等待计算执行完毕并获取结果*/
V get() throws InterruptedException, ExecutionException;
/*如果有必要,定时等待计算执行完毕(可能)并获取结果(如果结果有效)*/
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
我们来看一下:
boolean cancel(boolean mayInterruptIfRunning)
参数的意义:
如果参数mayInterruptIfRunning
为true,表明执行该任务的线程应该被中断。若为false,则允许正在执行的任务继续执行。
boolean isDone()
:这里需要注意的是,当前任务正常完成、抛出异常或者被取消,都会返回true。
这里介绍完Callable和Future两个接口,接下来便是本文的核心部分 —— Future唯一实现类FutureTask的源码分析。
三、FutureTask源码分析
3.1 Future继承结构图
我们可以看到,FutureTask实现了RunnableFutrue接口,而RunnableFutrue接口既继承了Future接口,又继承了Runnable接口,说明FutureTask既能作为Runnable被Thread执行,也能作为Future用来获取Callable的执行结果。
3.2 参数介绍
/*表示该任务的运行状态*/
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;
我们来看一下FutureTask的参数介绍:
- NEW: 该任务是个新的任务或者还没执行完成的任务,即初始状态。
- COMPLETING: 任务已经执行完成或者在执行任务的过程中发生异常,但是还未将任务执行的结果或者抛异常的原因保存到outcome字段的时候,状态会从
NEW
转换成COMPLETING
。该状态保持的时间比较短,所以属于一个中间状态。 - NORMAL: 该任务已经正常执行完毕并且将执行的结果保存到outcome字段中,状态会从
COMPLETING
转换成NORMAL
,该状态属于最终状态。 - EXCEPTIONAL: 该任务执行过程中发生异常并且将异常原因保存到outcome字段中,状态会从
COMPLETING
转换成EXCEPTIONAL
,该状态属于最终状态。 - CANCELLED:任务还未开始执行或者已经开始执行但是还没有执行完成的时候,用户调用
cancel(false)
函数取消该任务但是不中断任务执行线程,这个时候状态会从NEW转化成CANCELLED。该状态为最终状态。 - INTERRUPTING:任务还未开始执行或者已经开始执行但是还没有执行完成的时候,用户调用
cancel(ture)
函数取消任务并且中断任务执行线程,但是还未开始执行中断任务执行线程之前,状态会从NEW
转化成INTERRUPTING
状态。该状态属于中间状态。 - INTERRUPTED :调用interrupt()中断任务执行线程之后状态会从
INTERRUPTING
转换到INTERRUPTED
。这是一个最终状态。
FutureTask的状态转换主要有四条路线:
白色代表初始状态,黄色代表中间状态(临时状态),绿色则代表最终状态。
FutureTask的其他参数:
/*表示执行的业务逻辑*/
private Callable<V> callable;
/*当任务执行完毕后,任务返回的执行结果*/
private Object outcome; // non-volatile, protected by state reads/writes
/*任务执行的线程*/
private volatile Thread runner;
/*
当任务还没有执行完毕,用户调用get()方法时会被阻塞,被封装成WaitNode结点加入waiters中
waiters本身是一个单链表形式的存在
*/
private volatile WaitNode waiters;
3.3 构造函数
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; //当前任务的初始状态为NEW
}
该构造函数很简单,直接传入Callable实例对象,并设置该任务的初始状态为NEW。
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW;
}
/*调用Executors的callable将当前Runnable和result封装成一个Callable对象*/
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new Executors.RunnableAdapter<T>(task, result);
}
/*采用适配器的设计模式*/
static final class RunnableAdapter<T> implements Callable<T> {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}
该构造函数会把传入的runnable和result封装成一个Callable对象保存在callable字段中。当任务执行完成时,会返回传入的result值。
3.4. FutureTask的API源码分析
3.4.1. run():
前面说道,FutureTask可以被当成一个Runnable被执行,那么来看一下run()方法的具体实现。
public void run() {
/**
* 1. 如果当前任务的执行状态不为NEW,说明当前任务已经执行过了或者被取消了,则直接返回,不需要继续执行
* 2. 如果当前任务的执行状态为NEW,通过CAS尝试将当前线程和成员变量runner绑定,若失败,说明runner已经和其他
* 线程绑定了,那就直接返回
*/
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) {
/*call()执行过程中抛出异常*/
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
runner = null;
int s = state;
/*若任务是被中断,那么执行中断处理*/
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
/*等待该任务状态从INTERRUPTING转换成最终状态INTERRUPTED*/
private void handlePossibleCancellationInterrupt(int s) {
if (s == INTERRUPTING)
while (state == INTERRUPTING)
Thread.yield();
}
来梳理一下run() 的执行流程:
- 判断该任务的状态是否为NEW,不为NEW说明该任务已经被执行过或者被取消了,那么直接退出。
- 如果该任务的状态为NEW,利用CAS尝试将成员变量runner和当前线程绑定,若失败,说明runner已经和其他的线程绑定了,那么直接退出。
- 若该任务状态为NEW并且runner绑定当前线程成功,再次检验该任务的状态,若为NEW,则执行任务。
- 执行任务过程若发生异常,则采用
setException
函数来设置异常。 - 若执行成功且一切正常,则采用
set
函数来设置结果。
我们来看一下run函数中调用的其他函数:
setException
protected void setException(Throwable t) {
/*采用cas将当前任务的状态从NEW转换成COMPLETING*/
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
/*设置输出*/
outcome = t;
/*设置当前任务的状态为最终态EXCEPTIONAL*/
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
}
}
setException函数的流程:
- 调用CAS将当前任务的状态从
NEW
转换成COMPLETING
。 - 将异常原因保存在outcome字段中,outcome字段是用来保存任务执行结果或者异常原因的。
- 调用unsafe类将当前任务的状态设置为最终态
EXCEPTIONAL
。 - 调用
finishCompletion()
函数,该函数等分析完set
函数再说。
set
protected void set(V v) {
/*采用cas将当前任务的状态从NEW转换成COMPLETING*/
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
/*设置当前任务的状态为最终态NORMAL*/
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
set函数的流程:
5. 调用CAS将当前任务的状态从NEW
转换成COMPLETING
。
6. 将任务执行的结果保存在outcome字段中。
7. 调用unsafe类将当前任务的状态设置为最终态NORMAL
。
8. 调用finishCompletion()
函数。
setException
和set
的流程很相似,只是输出结果和任务的最终状态有差异而已。
接下来我们来看一个和run方法差不多的:
protected boolean runAndReset() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return false;
boolean ran = false;
int s = state;
try {
Callable<V> c = callable;
if (c != null && s == NEW) {
try {
c.call(); // don't set result
ran = true;
} catch (Throwable ex) {
setException(ex);
}
}
} finally {
runner = null;
s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
return ran && s == NEW;
}
这个函数和run()
函数十分相似,唯一的不同就是当前函数正常执行完毕后不会调用set
方法设置执行结果。看源码的解释是该方法是用来执行那些不止执行一次的任务。
3.4.2 get()
任务发起线程可以通过调用get()
方法来获取任务执行的结果,如果此时任务已经执行完毕,那么会直接返回结果。若任务还未执行完毕,那么调用方会阻塞直到任务执行完毕返回结果为止。
public V get() throws InterruptedException, ExecutionException {
/*获取该任务的运行状态*/
int s = state;
/*判断任务是否执行完毕,记得,COMPLETING只是一个中间状态,不算真正的执行完毕*/
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
get方法的流程如下:
- 判断任务是否执行完毕,如果没有,调用
awaitDone
进行阻塞等待。 - 如果执行完毕,调用
report
方法返回结果。
我们来看一下awaitDone
的源码实现:
我们先来看一下FutureTask的内部类WaitNode:
static final class WaitNode {
/*结点绑定的线程*/
volatile Thread thread;
/*当前结点的后继结点*/
volatile FutureTask.WaitNode next;
WaitNode() { thread = Thread.currentThread(); }
}
WaitNode是一个单向链表的结构,内部设置thread字段来绑定被阻塞的线程。如果任务还未执行完成,那么调用方的线程会被封装成一个WaitNode结点加入等待队列中。
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
/*若是定时阻塞等待, 则设置超时时间*/
final long deadline = timed ? System.nanoTime() + nanos : 0L;
FutureTask.WaitNode q = null;
boolean queued = false;
for (;;) {
/*如果线程被中断,如果被中断,则调用removeWaiter在等待队列中删除该结点并抛出异常*/
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
/*如果当前的状态大于COMPLETING,要么任务正常执行完毕,或者任务被取消,或者执行过程中抛出异常*/
if (s > COMPLETING) {
/*将等待队列相应结点所绑定的线程设置为null,并且返回当前任务的状态*/
if (q != null)
q.thread = null;
return s;
}
/*如果当前任务的状态处于中间状态COMPLETING,说明任务已经结果但是还未给outcome字段
赋值,那么这个时候当前线程调用Thread.yield()(可能)转让执行权 交给其他线程优先执行*/
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
/*如果任务还未开始执行完毕且q为null,那么构建一个WaitNode结点*/
else if (q == null)
q = new FutureTask.WaitNode();
/*如果当前结点还未加入等待队列中,那么通过cas的方式将当前结点加入等待队列中(waiters),这里使用的是头插法*/
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);
}
}
梳理一下awaitDone
的执行流程:
- 判断调用
get()
方法的线程是否被其他线程中断,如果被中断,那么调用removeWaiter
方法从等待队列中移除当前结点并且抛出中断异常。 - 判断任务是否执行完毕(前面我们讲过,只要状态大于COMPLETING,都算任务执行完毕),如果是,那么将当前结点绑定的线程置为空并返回结果。
- 判断线程是否处于中间状态COMPLETING,如果是,那么调用
Thread.yield()
转让执行权,让其他线程优先执行。 - 判断当前结点是否创建,若没有,则创建一个WaitNode结点。
- 判断新建结点是否加入等待队列中,若没有,采用CAS和头插法将结点加入到等待队列中。
- 将线程挂起。
以上过程会一直循环执行,直至线程被中断或者任务执行完毕。
我们来看一下结点是如何移除的。
private void removeWaiter(FutureTask.WaitNode node) {
/*若结点创建*/
if (node != null) {
/*将结点绑定线程置为空*/
node.thread = null;
retry:
for (;;) { // restart on removeWaiter race
for (FutureTask.WaitNode pred = null, q = waiters, s; q != null; q = s) {
s = q.next;
/*在等待队列中,thread为空的结点都为废弃结点*/
if (q.thread != null)
pred = q;
else if (pred != null) {
pred.next = s;
/*这里会再检测pred是否已经成为废弃结点,如果是,那么就重新开始遍历*/
if (pred.thread == null) // check for race
continue retry;
}
/*调用cas替换头结点,设置设置失败,重新开始遍历*/
else if (!UNSAFE.compareAndSwapObject(this, waitersOffset,
q, s))
continue retry;
}
break;
}
}
}
我们看到,removeWaiter
会不断遍历整个等待队列,移除thread字段为null(废弃的结点)的结点。
get(long timeout, TimeUnit unit)
和get函数差不多,这里就不再赘述。
3.4.3. cancel
接下来我们来分析一下是如何取消任务的。
public boolean cancel(boolean mayInterruptIfRunning) {
/*判断任务是否还处于NEW状态,若处于,根据mayInterruptIfRunning的值来将当前任务转换成
INTERRUPTING状态或者CANCELLED状态*/
if (!(state == NEW &&
UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try { // in case call to interrupt throws exception
/*如果mayInterruptIfRunning为true,则将任务进行中断*/
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();
} finally { // final state
/*调用unsafe类将当前任务的状态设置为INTERRUPTED,最终状态*/
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
}
cancel执行流程如下:
- 判断任务是否还处于NEW状态,若是,根据mayInterruptIfRunning的值来将当前任务转换成
INTERRUPTING状态或者CANCELLED状态。 - 如果
mayInterruptIfRunning
为true的话,那么将任务执行线程进行中断,并且将该任务的状态设置为INTERRUPTED。 - 调用
finishCompletion()
函数。
需要注意的是,执行cancel(true)
方法并不会立马让正在执行的任务停止,而是设置中断标记为true。至于怎么处理中断,需要看具体代码实现。
接下来我们来看一下finishCompletion()
,这个方法在任务执行正常完毕、任务执行过程中抛出异常、或者调用cancel方法取消任务时都出现过。
private void finishCompletion() {
// assert state > COMPLETING;
/*遍历等待队列,唤醒所有在等待队列上的线程*/
for (FutureTask.WaitNode q; (q = waiters) != null;) {
/*利用cas将等待队列设置为空*/
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
for (;;) {
/*获取结点所绑定的线程*/
Thread t = q.thread;
if (t != null) {
q.thread = null;
/*唤醒挂起的线程*/
LockSupport.unpark(t);
}
/*遍历当前结点的后继结点*/
FutureTask.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流程比较简单,遍历链表,唤醒相应的线程,最后将callable设置为空。done()方法在FutureTask中是没有任何实现的,由子类去实现。唤醒的线程会在awaitDone
方法中继续执行循环,在这次循环中会立刻返回任务的执行状态。
当任务执行完毕或者从awaitDown
方法中返回时,会调用report
返回返回执行结果。
private V report(int s) throws ExecutionException {
/*获取返回结果*/
Object x = outcome;
/*如果任务的执行状态为NORMAL,说明是正常执行完毕,那么直接返回执行结果*/
if (s == NORMAL)
return (V)x;
/*任务被取消,抛出CancellationException异常*/
if (s >= CANCELLED)
throw new CancellationException();
/*其他情况,抛出执行异常ExecutionException*/
throw new ExecutionException((Throwable)x);
}
以上便是FutureTask的全部源码分析了。
Demo1: 异步计算
public class FutureExample {
static class Task implements Callable<Integer>{
@Override
public Integer call(){
int result = 0;
try {
/*模拟耗时业务*/
Thread.sleep(4000);
result = 1 + 2;
System.out.println("子线程执行计算完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
return result;
}
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
long st = System.currentTimeMillis();
FutureTask<Integer> futureTask = new FutureTask<>(new Task());
new Thread(futureTask).start();
/*模拟耗时业务*/
Thread.sleep(2000);
int a = 1 + 2;
Integer result = null;
try {
result = futureTask.get(1000, TimeUnit.MILLISECONDS);
System.out.println("主线程执行计算完毕");
System.out.println("result: " + (a + result));
long ed = System.currentTimeMillis();
System.out.println("time: " + (ed - st) + "ms");
} catch (TimeoutException e) {
System.out.println("获取超时");
}
}
}
Demo2:并发场景下只有一个线程能执行该异步计算
public class FutureExample {
static class Task implements Callable<Integer>{
@Override
public Integer call(){
int result = 0;
try {
/*模拟耗时业务*/
Thread.sleep(4000);
result = 1 + 2;
System.out.println("子线程执行计算完毕");
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
return result;
}
}
static CountDownLatch latch = new CountDownLatch(5);
private static ExecutorService threadPool = Executors.newFixedThreadPool(5);
@Test
public void test() throws InterruptedException, ExecutionException {
FutureTask<Integer> task = new FutureTask<>(new Task());
for (int i = 0; i < 5; i++) {
threadPool.submit(task);
}
latch.await();
System.out.println("执行结果: " + task.get());
System.out.println("执行完毕");
}
}
执行结果:
子线程执行计算完毕
...(等待)
我们可以看到,我们利用线程池执行五次FutureTask,只出现一次子线程执行计算完毕。
参考:
- Java并发编程的艺术
- 深入学习 FutureTask