Java多线程(二):Callable&Future&FutureTask源码分析
1.创建线程任务方案一:Runnable
runnable无返回值,run实现线程逻辑
public interface Runnable {
public abstract void run();
}
2.创建线程任务方案二:Callable
2.1 Callable
callable有返回值(V),call实现线程逻辑
public interface Callable<V> {
V call() throws Exception;
}
2.2 Future
Callabe 不能单独使用,需要 Future 用来控制Callable执行,获取Callable执行结果。
public interface Future<V> {
// 如果任务已经成功了,或已经取消了,是无法再取消的,会直接返回取消成功(true)
// 如果任务还没有开始进行时,发起取消,是可以取消成功的。
// 如果取消时,任务已经在运行了,mayInterruptIfRunning 为 true 的话,就可以打断运行中的线程
// mayInterruptIfRunning 为 false,表示不能打断直接返回
boolean cancel(boolean mayInterruptIfRunning);
// 返回线程是否已经被取消了,true 表示已经被取消了
// 如果线程已经运行结束了,isCancelled 和 isDone 返回的都是 true
boolean isCancelled();
// 线程是否已经运行结束了
boolean isDone();
// 等待结果返回
// 如果任务被取消了,抛 CancellationException 异常
// 如果等待过程中被打断了,抛 InterruptedException 异常
V get() throws InterruptedException, ExecutionException;
// 等待,但是带有超时时间的,如果超时时间外仍然没有响应,抛 TimeoutException 异常
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
get 方法主要作用是得到 Callable 异步任务执行的结果,无参 get 会一直等待任务执行完成之后才返回,有参 get 方法可以设定固定的时间,在设定的时间内,如果任务还没有执行成功,直接返回异常,在实际工作中,建议多多使用 get 有参方法,少用 get 无参方法,防止任务执行过慢时,多数线程都在等待,造成线程耗尽的问题。
cancel 方法主要用来取消任务,如果任务还没有执行,是可以取消的,如果任务已经在执行过程中了,你可以选择不取消,或者直接打断执行中的任务
那到这里就有一个问题了, Callable 与 Future都是接口,怎么实现通过Future控制Callable呢?可以创建一个中间类实现Future接口,然后将Callable组合进来,最后通过Future接口中的方法实现控制。这里的具体逻辑可以在FutureTask中看到。
3.FutureTask
Runnable 和 Callable 都可以表示线程要执行的任务,那么这两个接口如何在不该变有关系的基础上互相转化?
首先,Runnable 与 Callable 肯定不能通过extends实现,因为直接继承就是is-a的关系,而这俩显然不是
那么还有一个办法,引入一个中间类 F(满足以下两个条件)
F extends Runnable:继承Runnable
F { primary Callable c }:组合Callable
==> 最终,结合上面的所有分析,我们得到了 FutureTask 的结构:实现Future接口 & 实现Runnable接口 & 组合Callable
public class FutureTask<V> implements RunnableFuture<V> {
// 任务状态
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;//任务被打断成功
// 组合了 Callable
private Callable<V> callable;
// 异步线程返回的结果
private Object outcome;
// 当前任务所运行的线程
private volatile Thread runner;
// 记录调用 get 方法时被等待的线程
private volatile WaitNode waiters;
//---------------------------构造方法---------------------------------
// 构造器中传入Callable接口实现对象,对callable成员变量进行初始化
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
// 任务状态初始化
this.state = NEW; // ensure visibility of callable
}
// 使用 Runnable 初始化,并传入 result 作为返回结果。
// Runnable 是没有返回值的,所以 result 一般没有用,置为 null 就好了
public FutureTask(Runnable runnable, V result) {
// Executors.callable 方法把 runnable 适配成 RunnableAdapter,
// RunnableAdapter 实现了callable,所以也就是把 runnable 直接适配成了 callable。
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
}
3.1 RunnableFuture、
首先来看一下 FutureTask 实现的 RunnableFuture 接口是什么
继承Runable:将外在变成Runnable
继承Future:实现Future对Callable的控制
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
下面我们就分别看看FutureTask是如何实现Future接口和Runnable接口中的方法
3.2 Future接口方法的实现
通过实现Future,组合Callable实现了两者的结合
构造器传入Callable
实现Future的方法进行返回结果操作
FutureTask实现了Runnable,当构造函数传入Callable就直接转化为了Runnable
get
get 有无限阻塞和带超时时间两种方法,我们通常建议使用带超时时间的方法,源码如下
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);
}
// 等待任务执行完成
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
// 计算等待终止时间,如果一直等待的话,终止时间为 0
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;
}
// 如果正在执行,当前线程让出 cpu,重新竞争,防止 cpu 飙高
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
// 如果第一次运行,新建 waitNode,当前线程就是 waitNode 的属性
else if (q == null)
q = new WaitNode();
// 默认第一次都会执行这里,执行成功之后,queued 就为 true,就不会再执行了
// 把当前 waitNode 当做 waiters 链表的第一个
else if (!queued)
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
// 如果设置了超时时间,并过了超时时间的话,从 waiters 链表中删除当前 wait
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
// 没有过超时时间,线程进入 TIMED_WAITING 状态
LockSupport.parkNanos(this, nanos);
}
// 没有设置超时时间,进入 WAITING 状态
else
LockSupport.park(this);
}
}
cancel
取消任务,如果正在运行,尝试去打断
public boolean cancel(boolean mayInterruptIfRunning) {
if (!(state == NEW &&//任务状态不是创建 并且不能把 new 状态置为取消,直接返回 false
UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
// 进行取消操作,打断可能会抛出异常,选择 try finally 的结构
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;
}
3.3 Runnable接口方法的实现
run
run 方法可以直接被调用,也可以开启新的线程进行调用
run 方法是没有返回值的,通过给 outcome 属性赋值(set(result)),get 时就能从 outcome 属性中拿到返回值;
FutureTask 两种构造器,最终都转化成了 Callable,所以在 run 方法执行的时候,只需要执行 Callable 的 call 方法即可,在执行 c.call() 代码时,如果入参是 Runnable 的话, 调用路径为 c.call() -> RunnableAdapter.call() -> Runnable.run(),如果入参是 Callable 的话,直接调用。
public void run() {
// 状态不是任务创建,或者当前任务已经有线程在执行了,直接返回
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
// 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);
}
// 给 outcome 赋值
if (ran)
set(result);
}
} finally {
runner = null;
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
3.4 runnale转化成callable
构造函数传入Runnable与返回值,转化后赋给callable
public FutureTask(Runnable runnable, V result){
// 将转化的Callable赋值给callable
this.callable = Executors.callable(runnable, result);
}
适配器模式:Executors.callable() --> RunnableAdpter
// 转化 Runnable 成 Callable 的工具类
// 自己实现Callable接口
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;
}
// 重写call方法,返回result
public T call() {
task.run();
return result;
}
}
最后,总结一下FutureTask 有什么作用:
实现了 Future 的所有方法,对任务有一定的管理功能,比如说拿到任务执行结果,取消任务,打断任务等等。
组合了 Callable,实现了 Runnable,把 Callable 和 Runnnable 串联了起来。
统一了有参任务和无参任务两种定义方式,方便了使用。
作者:A minor
来源:CSDN