使用JDK的FutureTask实现的异步调用l
Callable有返回值,和Runnable没有关系
public interface Callable<V> {
V call() throws Exception;
}
public interface Runnable {
public abstract void run();
}
FutureTask可以认为是一个Callable的适配器类,由于new Thread(Runnable)不能接收Callable,因此需要通过一个类进行适配——FutureTask,同时由于线程是没有返回值的,因此FutureTask需要有一个属性来储存Callable的返回值——outcome。
public class FutureTask<V> implements RunnableFuture<V> {
/** 状态,通过该状态判断任务是否完成 */
private volatile int state;
private static final int NEW = 0; // 初始化时为NEW
private static final int COMPLETING = 1; // 执行中
private static final int NORMAL = 2; // 可以理解为完成
private Callable<V> callable;
private Object outcome;
public V get() throws InterruptedException, ExecutionException {
int s = state;
// 如果当前状态为未完成,则循环等待
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s); // 返回结果(outcome)
}
// 这里只列出了两行关键代码
public void run() {
result = c.call(); // 调用callable的call()方法
set(result); // 见下面
// 简单点就是调用callable的call()方法,并
}
protected void set(V v) {
// 先将状态改为执行中
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v; // 将结果赋给outcome
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // 将状态修改为NORMAL
finishCompletion();
}
}
}
// 这里可以看到FutureTask继承了Future类
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
通过上面的代码,我们就知道Future的get()方法为什么是阻塞的,因为我们会循环判断Callable的call()方法是否执行完毕。
Future还支持有时间限制的等待,同样是阻塞的,这里不介绍了。
public V get(long timeout, TimeUnit unit)
下面通过一个示例演示JDK的Future实现的异步回调
创建一个Callable,计算1-10的和并返回,为了让阻塞更明显,这里让每次计算都等待0.3s
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableFutureDemo {
static class ComputeSumCallable implements Callable<Integer> {
/**
* 计算和
*/
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 10; i++) {
Thread.sleep(300); // 每次计算等待0.3s
sum += i;
}
return sum;
}
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 1)创建Callable
ComputeSumCallable computeSumCallable = new ComputeSumCallable();
// 2)创建FutureTask(可以认为是Callable的适配器)
FutureTask<Integer> futureTask = new FutureTask<Integer>(computeSumCallable);
// 3)创建一个线程并开始任务
new Thread(futureTask).start();
// 4)阻塞的调用结果
System.out.println("计算结果为:" + futureTask.get()); // 阻塞获取计算结果
}
}
也可以使用线程池直接执行Callable对象,会返回一个Future对象,使用instanceOf关键字可以看到,其实返回的Future对象是FutureTask的实例。(FutureTask继承了Future类,上面FutureTask源码最下面)
ExecutorService threadExecutor = Executors.newSingleThreadExecutor();
Future<Integer> future = threadExecutor.submit(computeSumCallable);
System.out.println("计算结果为:" + future.get()); // 阻塞获取计算结果
Guava的异步回调
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.0-jre</version>
</dependency>
接着对上面代码进行改写,使用Guava实现异步回调
这里可以先试着理解,然后再看源码,在最后把这段代码又贴了一遍。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
public class GuavaFutureDemo {
static class ComputeSumCallable implements Callable<Integer> {
/**
* 计算和
*/
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 10; i++) {
Thread.sleep(300); // 每次计算等待0.3s
sum += i;
}
return sum;
}
}
private static Integer sum = null;
public static void main(String[] args) throws InterruptedException {
// 创建10个线程的线程池
ExecutorService jdkThreadPool = Executors.newFixedThreadPool(10);
// 创建Guava的线程池,作用就是将JDK线程池返回的Future改为自己的ListenableFuture
ListeningExecutorService guavaThreadPool = MoreExecutors.listeningDecorator(jdkThreadPool);
// 创建Callable
ComputeSumCallable computeSumCallable = new ComputeSumCallable();
// 执行任务
ListenableFuture<Integer> computeListenableFuture = guavaThreadPool.submit(computeSumCallable);
// 创建回调对象,任务执行完后根据结果进行调用
FutureCallback<Integer> computeFutureCallback = new FutureCallback<Integer>() {
public void onSuccess(Integer result) {
sum = result;
}
public void onFailure(Throwable t) {
System.err.println("计算过程中出现错误!");
t.printStackTrace();
}
};
// 为ListenableFuture添加回调对象(监听者)
Futures.addCallback(computeListenableFuture, computeFutureCallback, guavaThreadPool);
while (sum == null) {
Thread.sleep(300);
System.out.println("干点别的~~~");
}
System.out.println("计算结果:" + sum);
guavaThreadPool.shutdown();
}
}
执行结果:
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
计算结果:55
在此之前,先介绍一下JDK与Guava中Future的不同。之后将对这个不同进行详细解释。(请理解这两句话的含义,貌似不难。顺便结合上面的示例理解。)
JDK中的Future是我们交代给甲任务,我们等着甲做完,然后自己处理后事。
Guava中的Future是我们交代给甲任务,甲做完后,交给乙处理后事。
Guava对JDK的Future进行了扩展,增加了监听者。这里的监听者,就是上面说到的乙。
public interface ListenableFuture<V> extends Future<V> {
void addListener(Runnable listener, Executor executor);
}
Guava中通过ExecutionList作为监听者列表,即一个Future可以有多个监听者,这些监听者通过链表形式存储。
public final class ExecutionList {
// RunnableExecutorPair就是一个链表结构
private RunnableExecutorPair runnables;
// 是否执行过(在Callable执行后才开始执行,执行完后executed为true)
private boolean executed;
public void add(Runnable runnable, Executor executor) {
synchronized (this) {
if (!executed) {
// 添加到监听者链表后面
runnables = new RunnableExecutorPair(runnable, executor, runnables);
return;
}
}
// 直接执行新添加的监听者方法
executeListener(runnable, executor);
}
public void execute() {
// 循环链表,调用executeListener(),即直接调用线程池执行Runnable
}
}
感觉上面解释的不是很清楚,这里用白话文解释一下:当Callable任务执行完毕,ListenableFuture调用内部ExecutionList对象的execute(),之后将executed设为true,如果这时我们再为ListenableFuture添加监听者,将直接调用线程池运行监听者的Runnable对象。(甲执行完了任务通知乙收尾)
上面我们知道了监听者是通过线程池执行的一个Runnable对象。上面说到,监听者是为了完成善后工作,也就是针对成功和失败进行不同的处理。
FutureCallback就是监听者最终要执行的任务。注意是最终,因为FutureCallback并不是一个Runnable类。也就是还要用Runnable对FutureCallback进行一层封装。这一层封装就是CallbackListener。
public interface FutureCallback<V> {
/** 成功执行的函数 */
void onSuccess(@Nullable V result);
/** 异常失败执行的函数 */
void onFailure(Throwable t);
}
private static final class CallbackListener<V> implements Runnable {
final Future<V> future;
final FutureCallback<? super V> callback;
CallbackListener( Future<V> future, FutureCallback<? super V> callback )
{
this.future = future;
this.callback = callback;
}
// 如果成功则传递Future返回值,否则传递异常
@Override
public void run()
{
final V value;
try {
value = getDone(future); // 获取Future的返回值
} catch ( ExecutionException e ) {
callback.onFailure(e.getCause()); // 异常则调用失败处理
return;
} catch ( RuntimeException | Error e ) {
callback.onFailure(e); // 异常则调用失败处理
return;
}
callback.onSuccess(value); // 否则调用成功处理
}
}
现在梳理一下,ListenableFuture添加监听者,当Callable执行完后,通知监听者执行善后任务。ListenableFuture添加监听者以及监听者执行善后任务我们已经理清了,剩下ListenableFuture如何通知监听者。
这里需要用到在最上面我们介绍FutureTask,首先是run()被调用,然后调用set(),set()的最后调用了finishCompletion(),在finishCompletion()的最后执行了done();这个done()方法FutureTask并没有实现。
public class FutureTask<V> implements RunnableFuture<V> {
public void run() {
result = c.call(); // 调用callable的call()方法
set(result); // 见下面
// 简单点就是调用callable的call()方法,并
}
protected void set(V v) {
// 先将状态改为执行中
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v; // 将结果赋给outcome
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // 将状态修改为NORMAL
finishCompletion();
}
}
private void finishCompletion() {
// 中间无关代码直接省略了
done();
}
protected void done() { }
}
public class ListenableFutureTask<V> extends FutureTask<V> implements ListenableFuture<V> {
/** 监听者列表 */
private final ExecutionList executionList = new ExecutionList();
/** 添加监听者 */
@Override
public void addListener(Runnable listener, Executor exec) {
executionList.add(listener, exec);
}
/** 调用监听者列表的execute()方法,该方法作用如果忘记可以回到上面看一下 */
@Override
protected void done() {
executionList.execute();
}
}
接着对上面代码进行改写,使用Guava实现异步回调
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
public class GuavaFutureDemo {
static class ComputeSumCallable implements Callable<Integer> {
/**
* 计算和
*/
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 10; i++) {
Thread.sleep(300); // 每次计算等待0.3s
sum += i;
}
return sum;
}
}
private static Integer sum = null;
public static void main(String[] args) throws InterruptedException {
// 创建10个线程的线程池
ExecutorService jdkThreadPool = Executors.newFixedThreadPool(10);
// 创建Guava的线程池,作用就是将JDK线程池返回的Future改为自己的ListenableFuture
ListeningExecutorService guavaThreadPool = MoreExecutors.listeningDecorator(jdkThreadPool);
// 创建Callable
ComputeSumCallable computeSumCallable = new ComputeSumCallable();
// 执行任务
ListenableFuture<Integer> computeListenableFuture = guavaThreadPool.submit(computeSumCallable);
// 创建回调对象,任务执行完后根据结果进行调用
FutureCallback<Integer> computeFutureCallback = new FutureCallback<Integer>() {
public void onSuccess(Integer result) {
sum = result;
}
public void onFailure(Throwable t) {
System.err.println("计算过程中出现错误!");
t.printStackTrace();
}
};
// 为ListenableFuture添加回调对象(监听者)
Futures.addCallback(computeListenableFuture, computeFutureCallback, guavaThreadPool);
while (sum == null) {
Thread.sleep(300);
System.out.println("干点别的~~~");
}
System.out.println("计算结果:" + sum);
guavaThreadPool.shutdown();
}
}
执行结果:
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
计算结果:55
如果有误,欢迎指正!