最近在学习Netty
,发现代码中到处都是Future
,很好奇它提供的的一些功能是怎么实现的:比如各种Callback
。虽然我猜想应该和某些设计模式相关,比如说观察者模式
,但还是很想了解它的实现原理。
其实,除了Netty中的ChannelFuture
,平时我们在其它地方也经常会遇到各种各样的Future
,比如:JDK中的FutureTask
、Guava中的ListenableFuture
、Guava中的FutureCallback
、Spring中的ListenableFutureTask
等,它们都提供了丰富的API,更满足于我们编程时遇到的各种场景。
这篇文章,我主要会从以下两个方面来介绍Future
:
1、Future
方法为什么可以拿到异步任务的执行结果?
2、Future
是如何处理各种回调的?
获取异步结果
在介绍上面3个东东之前,我们先来讨论一个问题:如何获取异步线程的执行结果?
假如没有那些API,你会如何获取异步线程的执行结果呢?在不同场景下,方式也不一样
全局变量
private volatile static Integer result;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
System.out.println("处理业务逻辑");
result = 1000;
}).start();
Thread.sleep(1000);
System.out.println(result);
}
while(result == null){
sleep(100)
}
- 将异步线程的执行结果赋值给一个全局变量
- 当前线程根据不同场景选择不同的方式来获取:比如
轮询+阻塞
;或者干脆不获取
,有时候我们可能会遇到这种场景,比如通过定时任务去更新缓存,不需要关注任务什么时候执行完成,我需要的只是缓存的值,任务执行了就获取最新的值,没有执行就获取旧值
简单封装
或许可以封装一下?在封装之前,先考虑几个问题:
- 任务的逻辑定义在哪里?如果用Runnable,就无法返回值,所以可以定义一个有返回值的@FunctionalInterface接口,叫
Task
- 返回的值存到哪里?怎么返回?Thread没有相关的方法,扩展一下?
public static void main(String[] args) throws InterruptedException {
CallableThread callableThread = new CallableThread(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "ccccc";
});
callableThread.start();
System.out.println("开始时间 " + LocalDateTime.now());
System.out.println(callableThread.get());
System.out.println("结束时间 " + LocalDateTime.now());
}
class CallableThread<T> extends Thread {
private Task<T> task;
private T result;
private volatile boolean finished = false;
public CallableThread(Task<T> task) {
this.task = task;
}
@Override
public void run() {
result = task.call();
finished = true;
notifyAll();
}
public T get() {
while (!finished) {
wait();
}
return result;
}
}
@FunctionalInterface
interface Task<T> {
T call();
}
这样貌似也可以,但是不太好:
- Thread本来只是用于处理和线程相关的事情,现在将它和逻辑(Task)绑定在一起,如果有多个任务想共用一个Thread,那返回值怎么处理?
- 是否可以将这部分逻辑抽出来,放到一个新类当中?
简单封装优化
public static void main(String[] args) throws InterruptedException {
MyRunnable<String> myRunnable = new MyRunnable(() -> {
// 模拟耗时的业务操作
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "我是结果";
});
System.out.println("开始时间 " + LocalDateTime.now());
new Thread(myRunnable).start();
System.out.println("result: " + myRunnable.get());
System.out.println("结束时间 " + LocalDateTime.now());
}
class MyRunnable<T> implements Runnable {
private Task<T> task;
private T result;
private volatile boolean finished = false;
public MyRunnable(Task<T> task) {
this.task = task;
}
@Override
public void run() {
result = task.call();
finished = true;
notifyAll();
}
public T get() {
while (!finished) {
wait();
}
return result;
}
}
这不是和Java里面的Future
有点像了?
Future
下面我们来介绍一下Java中的Future
基本使用
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> future = new FutureTask<>(() -> {
Thread.sleep(3000);
System.out.println(System.currentTimeMillis());
return "hehehh";
});
new Thread(future).start();
System.out.println("Start Get Result : " + System.currentTimeMillis());
System.out.println("Get Result : " + future.get() + System.currentTimeMillis());
}
原理分析
Future模式
中有几个比较核心的概念:
- Future:抽象出
获取任务返回值
、获取任务执行状态
等常用方法的接口 - Callable:任务的载体,类似于上面的
Task
- FutureTask:类似于上面的
MyRunnable
,它同时实现了Runnable
和Future
接口
Future接口
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
FutureTask
FutureTask
同时实现了Runnable
和Future
接口,所以它除了是任务的载体,还提供了一个用于操作任务的API:获取执行结果
、取消任务
、判断任务是否执行完成
等
任务状态
任务状态
,即表示任务在运行过程中的状态,随着任务的执行,状态将不断地进行转变。在FutureTask
中,任务的不同状态通过state
变量来表示,状态有以下几种:
/*
* NEW -> COMPLETING -> NORMAL
* NEW -> COMPLETING -> EXCEPTIONAL
* NEW -> CANCELLED
* NEW -> INTERRUPTING -> INTERRUPTED
*/
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
实现了Runnable
接口,所以主要关注它的run
方法:
1、先判断状态,如果不为NEW
或者已经有线程执行过了,就直接返回,否则进入下面逻辑
2、执行Callable#call
方法,根据执行结果,设置状态,这里主要有两种情况:
- 执行成功:先将state设置成
COMPLETING
,然后保存返回的结果保存到属性outcome
,再将state设置成NORMAL
,最后通过LockSupport.unpark(t)
解除阻塞的线程 - 执行失败:先将state设置成
COMPLETING
,然后异常信息保存到属性outcome
,再将state设置成EXCEPTIONAL
,最后通过LockSupport.unpark(t)
解除阻塞的线程
public void run() {
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) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
}
}
protected void setException(Throwable t) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t;
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
}
}
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
获取结果
我们可以通过FutureTask#get
方法获取异步任务的执行结果,但这是如何做到的呢?当我们执行FutureTask#get
方法时
- 如果异步任务已经执行完成,我们可以直接获取结果返回
- 如果异步任务还没有执行完成,当前线程会被阻塞,直到任务执行完成才会唤醒
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
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;
// state > COMPLETING ,说明任务要么正常执行,要么异常结束,所以这里可以直接返回
if (s > COMPLETING) {
if (q != null)
q.thread = null; // 这应该是help GC吧?
return s;
}
// 如果正在收尾阶段,交出CPU, 等下次循环
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
else if (q == null)
q = new WaitNode();
// 通过UNSAFE 设置 waiters
else if (!queued)
// 将新的`WaitNode`添加到单向链表的头部,waiters即对应头节点
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); // 阻塞当前线程
}
}
这里想象一个场景:FutureTask
对象已经暴露出去了,那代表可以别被多个线程使用,假如在异步任务执行完成之前,多个线程都调用了FutureTask#get
方法,该如何让这些线程阻塞呢?
- 针对不同的线程,封装成不同的
WaitNode
对象,然后将这些WaitNode
对象组装成一颗单向链表。新的节点会添加到头部 - 通过
LockSupport.park
阻塞当前线程 - 在任务执行完成的时候,会执行
finishCompletion
方法,主要就是从头节点依次往下遍历,获取节点的thread
属性,然后执行LockSupport.unpark(thread)
方法唤醒阻塞的线程
ListenableFuture
回调
FutureTask#get
方法可以获取异步任务的返回值,但假如我想在获取返回值之后执行一些其他的逻辑该怎么处理呢?其实我最直接的想法就是回调。比如,我们可以对上面的MyRunnable
代码再扩展一下,例如
public MyRunnable addListener(Consumer c) {
// 这里是一个例子,肯定不会每次都new一个线程,一般是使用线程池
new Thread(()->{
while (!finished) {
Thread.sleep(10);
}
c.accept(result);
}).start();
return this;
}
我们给MyRunnable
添加了一个addListener
方法,接收一个Consumer
作为入参,当任务执行完成之后就执行这段逻辑,如下:
public static void main(String[] args) throws InterruptedException {
MyRunnable<String> myRunnable = new MyRunnable(() -> {
// 模拟耗时的业务操作
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "我是结果";
});
System.out.println("开始时间 " + LocalDateTime.now());
new Thread(myRunnable).start();
myRunnable.addListener(result -> {
System.out.println("当xxx执行完成之后,线程:" + Thread.currentThread().getName() + " 执行一些其他的任务");
result = result + " ggggg";
System.out.println(result);
});
}
ListenableFuture#addListener
ListenableFuture
是guava
包里面的,对Future
进行了增强,ListenableFuture
继承了Future
,新增了一个添加回调的方法
/**
* @param listener the listener to run when the computation is complete 回调逻辑
* @param executor the executor to run the listener in 回调在哪个线程池执行
*/
void addListener(Runnable listener, Executor executor);
ListenableFutureTask
继承了FutureTask
并且是实现了ListenableFuture
接口,看一个简单例子
public static void main(String[] args) throws InterruptedException {
ListenableFutureTask futureTask = ListenableFutureTask.create(() -> {
System.out.println("执行任务开始 " + LocalDateTime.now());
Thread.sleep(3000);
System.out.println("执行任务完成 " + LocalDateTime.now());
return "结果";
});
futureTask.addListener(() -> System.out.println("获取结果之后,输出一条日志"), MoreExecutors.directExecutor());
new Thread(futureTask).start();
}
源码分析
原理就是将所有回调维护在一个单向链表中,也就是ExecutionList
,然后通过重写FutureTask#done
方法,在任务完成之后执行回调逻辑
// 每个回调就相当于是一个RunnableExecutorPair节点,所有RunnableExecutorPair节点构成一条链表,头插链表
private final ExecutionList executionList = new ExecutionList();
// ListenableFutureTask#addListener
public void addListener(Runnable listener, Executor exec) {
executionList.add(listener, exec);
}
// ExecutionList#add
public void add(Runnable runnable, Executor executor) {
// 上锁,因为它的内部属性 executed 可能会被任务逻辑线程更新,即 ListenableFutureTask 实现了 FutureTask 的done方法,然后会在里面更新 executed 的值为true
// 还有一点,如果不加锁,当多个线程同时添加回调的时候,可能会造成节点丢失
synchronized (this) {
// 如果任务还没有执行完成,就将当前节点添加到头节点
if (!executed) {
runnables = new RunnableExecutorPair(runnable, executor, runnables);
return;
}
}
// 如果任务执行完成,就开始执行回调
executeListener(runnable, executor);
}
// ExecutionList#executeListener
private static void executeListener(Runnable runnable, Executor executor) {
try {
// 直接将任务交给线程池
executor.execute(runnable);
} catch (RuntimeException e) {
log.log(Level.SEVERE, "RuntimeException while executing runnable " + runnable + " with executor " + executor, e);
}
}
// ExecutionList.RunnableExecutorPair
private static final class RunnableExecutorPair {
final Runnable runnable;
final Executor executor;
@Nullable RunnableExecutorPair next;
RunnableExecutorPair(Runnable runnable, Executor executor, RunnableExecutorPair next) {
this.runnable = runnable;
this.executor = executor;
this.next = next;
}
}
ListenableFutureTask
是怎么知道任务是否执行完成了呢?
在FutureTask#finishCompletion
方法中,解除阻塞的线程之后,还会执行一个done
方法,不过该方法在FutureTask
没有任何逻辑,可以把它当作是一个模板方法,而ListenableFutureTask
实现了该方法,如下:
// ListenableFutureTask#done
protected void done() {
executionList.execute();
}
// ExecutionList#execute
public void execute() {
RunnableExecutorPair list;
synchronized (this) {
if (executed) {
return;
}
// 首先将executed置为true
executed = true;
// runnables代表链表的头节点
list = runnables;
runnables = null; // allow GC to free listeners even if this stays around for a while.
}
RunnableExecutorPair reversedList = null;
// 这其实是一个倒置的过程,因为我们添加节点的时候,是插入到头部的,为了保证回调按照我们添加时的顺序执行,即 先添加先执行,所以做了一个倒置
while (list != null) {
RunnableExecutorPair tmp = list;
list = list.next;
tmp.next = reversedList;
reversedList = tmp;
}
// 遍历链表,依次执行回调逻辑
while (reversedList != null) {
executeListener(reversedList.runnable, reversedList.executor);
reversedList = reversedList.next;
}
}
FutureCallback
通过ListenableFutureTask
,我们可以在任务执行完成之后执行一些回调逻辑。可是细心的同学会发现,回调方法无法使用任务的返回值
,那假如我就是想先获取值然后再用这个返回值做下一步操作怎么办?还是只能先通过get方法阻塞当前线程吗?其实guava
包中也给了我们相关的接口。先看一个例子:
public static void main(String[] args) throws InterruptedException {
ListenableFutureTask futureTask = ListenableFutureTask.create(() -> {
System.out.println("执行任务开始 " + LocalDateTime.now());
Thread.sleep(3000);
System.out.println("执行任务完成 " + LocalDateTime.now());
return "结果";
});
Futures.addCallback(futureTask, new FutureCallback<String>() {
@Override
public void onSuccess(String result) {
System.out.println("执行成功: " + result);
}
@Override
public void onFailure(Throwable t) {
System.out.println("执行失败");
}
});
new Thread(futureTask).start();
}
源码分析
FutureCallback
接口里面有两个方法,分别对应任务执行成功逻辑和任务失败逻辑
void onSuccess(@Nullable V result);
void onFailure(Throwable t);
Futures
可以堪称是一个门面类,里面封装了一些操作
// Futures#addCallback
public static <V> void addCallback(
ListenableFuture<V> future, FutureCallback<? super V> callback) {
// 这里使用了DirectExecutor线程池,即直接在当前线程执行
addCallback(future, callback, directExecutor());
}
// Futures#addCallback
public static <V> void addCallback(final ListenableFuture<V> future, final FutureCallback<? super V> callback, Executor executor) {
Runnable callbackListener =
new Runnable() {
@Override
public void run() {
final V value;
try {
value = getDone(future);
} catch (ExecutionException e) {
callback.onFailure(e.getCause());
return;
} catch (RuntimeException e) {
callback.onFailure(e);
return;
} catch (Error e) {
callback.onFailure(e);
return;
}
callback.onSuccess(value);
}
};
// 最终还是将这部分逻辑封装成一个回调,然后在这个回调中获取返回值,根据返回值的结果执行相应的FutureCallback方法
future.addListener(callbackListener, executor);
}
// Futures#getDone
public static <V> V getDone(Future<V> future) throws ExecutionException {
checkState(future.isDone(), "Future was expected to be done: %s", future);
return getUninterruptibly(future);
}
public static <V> V getUninterruptibly(Future<V> future) throws ExecutionException {
boolean interrupted = false;
try {
while (true) {
try {
return future.get();
} catch (InterruptedException e) {
interrupted = true;
}
}
} finally {
if (interrupted) {
Thread.currentThread().interrupt();
}
}
}
本质上其实就是将获取返回值的逻辑封装成一个回调,在这个回调中获取返回值,根据返回值的结果执行相应的FutureCallback
方法,不过在使用上却方便了好多。
与我们直接通过get方法获取返回值然后再执行其他逻辑还是有区别的,因为我们直接调用Future#get
方法会阻塞当前线程,而guava
是在回调中执行这部逻辑,类似于一种通知机制,所以不会阻塞当前线程。
ListenableFutureTask
其实Spring里面也有一个ListenableFutureTask
,实现上和guava
大同小异,也是继承了FutureTask
并且实现了自己的ListenableFuture
接口,通过重写FutureTask#done
方法,在该方法中获取返回值然后执行回调逻辑
public static void main(String[] args) {
ListenableFutureTask future = new ListenableFutureTask(() -> "结果");
future.addCallback(new ListenableFutureCallback() {
@Override
public void onSuccess(Object result) {
System.out.println("callback " + result);
}
@Override
public void onFailure(Throwable ex) {
System.out.println("执行失败 ");
}
});
new Thread(future).start();
}
源码分析
它的Callback是保存在两个Queue里面的:successCallbacks
,failureCallbacks
,数据结构是LinkedList
private final Queue<SuccessCallback<? super T>> successCallbacks = new LinkedList<SuccessCallback<? super T>>();
private final Queue<FailureCallback> failureCallbacks = new LinkedList<FailureCallback>();
重写的done方法如下,逻辑很简单,就不解释了
protected void done() {
Throwable cause;
try {
T result = get();
this.callbacks.success(result);
return;
}catch (InterruptedException ex) {
Thread.currentThread().interrupt();
return;
}catch (ExecutionException ex) {
cause = ex.getCause();
if (cause == null) {
cause = ex;
}
}
catch (Throwable ex) {
cause = ex;
}
this.callbacks.failure(cause);
}
CompletableFuture
Netty中的Future
感觉自己现在对Netty中Future
和Promise
的理解还不是很深刻,很难组织出很丝滑
的语言。等自己对Netty有了更深刻的理解,再补充吧