并发编程(三)Promise, Future 和 Callback
异步操作的有两个经典接口:Future 和 Promise,其中的 Future 表示一个可能还没有实际完成的异步任务的结果,针对这个结果可以添加 Callback 以便在任务执行成功或失败后做出对应的操作,而 Promise 交由任务执行者,任务执行者通过 Promise 可以标记任务完成或者失败。 可以说这一套模型是很多异步非阻塞架构的基础。
这一套经典的模型在 Scala、C# 中得到了原生的支持,但 JDK 中暂时还只有无 Callback 的 Future 出现,当然也并非在 Java 界就没有发展了,比如 Guava 就提供了 ListenableFuture 接口,而 Netty 4+ 更是提供了完整的 Promise、Future 和 Listener 机制。
一、Future 模式 - 将来式(JDK)
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<Integer> future = executorService.submit(() -> {
TimeUnit.SECONDS.sleep(5);
return 5;
});
Integer result = future.get();
二、Future 模式--回调式(Guava)
Future 模式的第二种用法便是回调。很不幸的事,JDK 实现的 Future 并没有实现 callback, addListener 这样的方法,想要在 JAVA 中体验到 callback 的特性,得引入一些额外的框架。
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>21.0</version>
</dependency>
ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
ListenableFuture<Integer> future = service.submit(new Callable<Integer>() {
public Integer call() throws Exception {
TimeUnit.SECONDS.sleep(5);
return 100;
}
});
Futures.addCallback(future, new FutureCallback<Integer>() {
public void onSuccess(Integer result) {
System.out.println("success:" + result);
}
public void onFailure(Throwable throwable) {
System.out.println("fail, e = " + throwable);
}
});
Thread.currentThread().join();
三、Future 模式--回调式(Netty4)
Netty 除了是一个高性能的网络通信框架之外,还对 jdk 的Future 做了扩展,引入 Netty 的 maven 依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.22.Final</version>
</dependency>
EventExecutorGroup group = new DefaultEventExecutorGroup(4); // 4 threads
io.netty.util.concurrent.Future<Integer> f = group.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
TimeUnit.SECONDS.sleep(5);
return 100;
}
});
f.addListener(new FutureListener<Object>() {
@Override
public void operationComplete(io.netty.util.concurrent.Future<Object> objectFuture) throws Exception {
System.out.println("计算结果::"+objectFuture.get());
}
});
四、由 Callback Hell 引出 Promise 模式
同样的如果你对 ES6 有所接触,就不会对 Promise 这个模式感到陌生,如果你对前端不熟悉,也不要紧,我们先来看看回调地狱(Callback Hell)是个什么概念。
回调是一种我们推崇的异步调用方式,但也会遇到问题,也就是回调的嵌套。当需要多个异步回调一起书写时,就会出现下面的代码(以 js 为例):
asyncFunc1(opt, (...args1) => {
asyncFunc2(opt, (...args2) => {
asyncFunc3(opt, (...args3) => {
asyncFunc4(opt, (...args4) => {
// some operation
});
});
});
});
虽然在 Java 业务代码中很少出现回调的多层嵌套,这样的代码不易读,嵌套太深修改也麻烦。于是 ES6 提出了 Promise 模式来解决回调地狱的问题。可能就会有人想问:Java 中存在 Promise 模式吗?答案是肯定的。
前面提到了 Netty 和 Guava 的扩展都提供了 addListener 这样的接口,用于处理 Callback 调用,但其实 jdk1.8 已经提供了一种更为高级的回调方式:CompletableFuture。首先尝试用 CompletableFuture 来解决回调的问题。
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
TimeUnit.SECONDS.sleep(5);
return 100;
});
completableFuture.whenComplete((result, e) -> {
System.out.println("结果:" + result);
});
Thread.currentThread().join();
五、Future 和 Promise
Netty 文档说明 Netty 的网络操作都是异步的, 在源码上大量使用了 Future/Promise 模型,在 Netty 里面也是这样定义的:
- Future 接口定义了 isSuccess(), isCancellable(), cause() 这些判断异步执行状态的方法。(read-only)
- Promise 接口在 extends Future 的基础上增加了 setSuccess(), setFailure() 来标记任务完成或者失败。(writable)
JDK 的 Future
public interface Future<V> {
// 取消异步操作
boolean cancel(boolean mayInterruptIfRunning);
// 异步操作是否取消
boolean isCancelled();
// 异步操作是否完成,正常终止、异常、取消都是完成
boolean isDone();
// 阻塞直到取得异步操作结果
V get() throws InterruptedException, ExecutionException;
// 同上,但最长阻塞时间为timeout
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
Netty 对 JDK 的 Future 进行了扩展
public interface Future<V> extends java.util.concurrent.Future<V> {
// 异步操作完成且正常终止
boolean isSuccess();
// 异步操作是否可以取消
boolean isCancellable();
// 异步操作失败的原因
Throwable cause();
// 添加一个监听者,异步操作完成时回调,类比javascript的回调函数
Future<V> addListener(GenericFutureListener<? extends Future<? super V>> listener);
Future<V> removeListener(GenericFutureListener<? extends Future<? super V>> listener);
// 阻塞直到异步操作完成
Future<V> await() throws InterruptedException;
// 同上,但异步操作失败时抛出异常
Future<V> sync() throws InterruptedException;
// 非阻塞地返回异步结果,如果尚未完成返回null
V getNow();
}
Netty 的 Promise 对又对 Future 进行了扩展
public interface Promise<V> extends Future<V> {
Promise<V> setSuccess(V result);
boolean trySuccess(V result);
Promise<V> setFailure(Throwable cause);
boolean tryFailure(Throwable cause);
boolean setUncancellable();
}
DefaultChannelPromise 是 ChannelPromise 的实现类,它是实际运行时的 Promoise 实例。
参考:
- 《并发编程 Promise, Future 和 Callback》:https://ifeve.com/promise-future-callback/
每天用心记录一点点。内容也许不重要,但习惯很重要!