Future的真面目!
Future获取线程的执行结果
1、一般来说,在线程执行完毕之后,有两种情况,一种是关心线程的执行结果,一种是不关心线程执行结果。而Future则是属于第一种!
2、Future 如何获取执行结果呢?阻塞主线程等待子线程执行完毕又是如何实现的呢?
Future 的实现 FutureTask
我们先来剖析一下submit这个方法,它重载了三个方法,分别是Runnable接口,Runnable接口和返回类型范型,还有Callable。但是实际上都是callable,为什么我会这这样说呢,进一步来看看
`
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
`
这里可以看到,执行的是被封装的FutureTask 任务,这个任务里面又有什么呢?接着看
`
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
`
Executors.callable(runnable, result) 这个是在干嘛呢?接着看
`
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}
`
RunnableAdapter 这类就很关键了,这是runnable转换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;
}
public T call() {
task.run();
return result;
}
}
`
所以能看出来,实际上就是把runnable 的执行用callable包裹了起来,统一执行都是执行call方法再来调用真正的run方法,为什么要这样做呢?看看futureTask 的run()
`
public void run() {
// 判断线程状态不是new 或者执行线程不是当前线程
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 {
// 调用runnableAdapter 的 call()
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);
}
}
// 设置终态NORMAL,这里可以看出COMPLETING 这个状态是一个很短暂的中间状态
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
// 最终完善
finishCompletion();
}
}
// 设置终态EXCEPTIONAL,这里可以看出COMPLETING 这个状态是一个很短暂的中间状态
protected void setException(Throwable t) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t;
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
}
}
`
run()方法里面做了两件事,一个是调用了真正runnable的实现run()方法,另一个事情就是把返回结果处理了。这里也能看出为什么统一都是callable调用,因为可以做到一致性处理,对于callable来说,runnable就是另一种返回值设置为void的一种特殊情况,但是不管返回的可能是异常也可能是正常结果,但是这都体现在state的终态不一样,future的状态一共有以下几种
`
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;
`
正常的状态应该就是到2,也就是NORMAL,所以state >2 都是不正常的线程执行结果。在设置完状态之后还执行了一个很重要的操作就是 finishCompletion();那么这个方法是在干啥,接着看
`
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;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
// 钩子方法,内部需要子类实现
done();
callable = null; // to reduce footprint
}
`
这里其实就是解锁阻塞的线程,在状态还没有到终态的线程,在执行get()方法的时候就会被LockSupport.park();只有在刷新状态之后才会unpark(t)。get()方法比较简单,我们浅看一下
`
public V get() throws InterruptedException, ExecutionException {
int s = state;
// 状态非终态,需要阻塞当前线程
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
// 组装返回的报告,要么把返回值设置进outcome,要么抛出异常
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);
}
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();
// 初始化第一个节点,把当前线程设置进入thread属性
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);
}
}
`
所以这里很明显就看出get()方法在不是终态的时候会阻塞主线程,只有在子线程执行完毕,执行了set()或者setException()的时候,设置了终态为2或者3的时候,才会去唤醒阻塞的主线程。这就实现了主线程等待子线程执行完毕的整个过程。
总结
1、futureTask在submit的时候被创建,同时将实现封装进了RunnableAdapter,最终作为futureTask 的callable 属性
2、当线程被执行的时候,是直接调用futureTask 的run()方法,借助RunnableAdapter适配器去执行真正的实现run()方法,最终将结果返回
3、封装返回结果,有异常就将终态置为3,没异常正常执行终态就置为2.接着给主线程发放许可(所以只要执行够快,get()方法就不会发生阻塞)
4、调用get()方法获取执行结果,终态就直接返回,或抛出异常。非终态就阻塞主线程(这里其实原理是先执行unpark()的话,再执行park()则不会发生阻塞,可以直接获取到许可,但是park()先执行则发生阻塞,直到unpark()执行给了许可)
其实这里的状态还有其他的关闭,中断,被中断等,这都是和执行的线程息息相关。关闭则是调用方主动发起的(关闭有风险,子线程没有执行完毕的时候提前关闭会引发CancellationException异常)。另外get()也可以设置阻塞的时间限制,这个时间一般不好拿捏,如果不关心结果则可以设置。
本文来自博客园,作者:程序媛-肖,转载请注明原文链接:https://www.cnblogs.com/little-xiao/p/16845451.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构