异步编程在 netty 中应用非常多,源自 netty 的 Future 和 Promise接口,它们和 Java 的 Future 框架有什么联系呢?
编码层面异步的实现至少依赖于两个线程,Java 中的线程对应到 Thread 类。而 定义线程执行的任务有如下方式:
- 实现 Runnable 接口
- 继承 Thread 并重写run方法
- 实现 Callable 接口
Callable 接口定义是可以返回结果或者抛出 checked exception,而 Runnable 则不行。
什么异常属于 checked exception 呢?异常体系最上层是 Throwable 类,有两个子类是 Error 和 Exception。Exception 最有名的一个子类 RuntimeException 就是我们编码时常见的异常,比如它的子类 NullPointException。
Checked exception 就是 Exception 的子类中除了 RuntimeException 类型的异常,比如 IOException。
Callable 经常和Future 家族连用,Future表示:A Future represents the result of an asynchronous computation .
简单看个案例:
public static void main(String[] args) {
Callable callable = new Callable() {
@Override
public Object call() throws Exception {
return "执行结果";
}
};
FutureTask futureTask = new FutureTask(callable);
new Thread(futureTask).start();
}
创建一个 Callable 的实现,然后利用 FutureTask 包装后,创建新的 Thread 线程执行。这样就不阻塞main线程,可以在之后通过 futureTask.get() 获取执行结果。
而 FutureTask 实现了 Runnable 接口,所以可以交给 Thread 来创建线程。在 FutureTask 的 run 方法中(也即 Runnable 定义的):
public void run() {
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
// 这里真正调用了 call() 方法,也即定义了该线程的执行内容。(注意,此时执行run()方法的就是main中新建的线程)
result = c.call();
ran = true;
}
}
}
call() 执行后的结果 result 会被存储到 FutureTask 的成员变量 outcome 中。然后我们调用 get() 方法就会获取到 call() 的执行结果。
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
private V report(int s) throws ExecutionException {
// x就是 call() 的执行结果
Object x = outcome;
if (s == NORMAL)
return (V)x;
}
但是,其中 awaitDone 方法,会阻塞调用线程,简要代码如下:
private int awaitDone(boolean timed, long nanos) throws InterruptedException {
for (;;) {
...
int s = state;
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
...
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos);
}
else
LockSupport.park(this);
}
}
可见,main线程调用 get 方法时,实际上是要等待 call() 方法执行完成,或者执行超时。
现在,让我们进入 netty领域 的 Future框架。
public interface Future<V> extends java.util.concurrent.Future<V> {
/**
* Adds the specified listener to this future. The
* specified listener is notified when this future is
* {@linkplain #isDone() done}. If this future is already
* completed, the specified listener is notified immediately.
*/
Future<V> addListener(GenericFutureListener<? extends Future<? super V>> listener);
// 还有其他新接口
...
}
可见,最重要的是添加了 listener 回调机制。英文注释说明的很清晰:当 future 执行完成后,会通知(回调)listener。
Future 有两个非常重要的子接口,分别为:ChannelFuture 和 Promise。
/**
* Special {@link Future} which is writable.
*/
public interface Promise<V> extends Future<V> {
/**
* Marks this future as a success and notifies all
* listeners.
*
* If it is success or failed already it will throw an {@link IllegalStateException}.
*/
Promise<V> setSuccess(V result);
/**
* Marks this future as a failure and notifies all
* listeners.
*
* If it is success or failed already it will throw an {@link IllegalStateException}.
*/
Promise<V> setFailure(Throwable cause);
...
Promise 主要是多了可以设置该 Future 成功或者失败,并通知所有的监听者。类注释说明 Promise 是一种特殊的 Future,特殊在哪里呢?
Promise is writable。也就是可以修改 Future 的状态.
关于 Promise 的作用先暂停,这里要插入看一下 ChannelFuture ,然后再来看 Promise 的具体实现进行体会。
public interface ChannelFuture extends Future<Void> {
/**
* Returns a channel where the I/O operation associated with this
* future takes place.
*/
Channel channel();
}
主要是多了一个 channel() 方法,用于获取 Future 关联的 channel,channel 是 netty 中非常重要的模型,我们以后再说。
ChannelFuture 接口的注释写的非常详细,通过注释,我们明白了以下几点:
-
Future 的状态分类如下:
- Completed:执行结束
- Succeeded
- failed
- Cancelled
- unCompleted:执行中
- Completed:执行结束
-
Future的初始状态是 uncompleted,只有执行结束后,才会有 成功、失败、已取消三种状态。
-
当 Future 执行结束后,
isDone()方法就会返回 ture,否则都是 false。
这三点很重要,再看下 Promise 的注释:
/**
* Marks this future as a success and notifies all
* listeners.
*/
Promise<V> setSuccess(V result);
也就是 setSuccess 方法会标记 future 为 成功,也即 isDone 方法肯定是 ture。
有了上述铺垫,接着来看 ChannelPromise。
/**
* Special {@link ChannelFuture} which is writable.
*/
public interface ChannelPromise extends ChannelFuture, Promise<Void> {
// 基本都是继承的方法
...
}
特殊点在于它同时继承了 ChannelFuture 和 Promise 两个接口。它属于 ChannelFuture,但又拥有 Promise 的能力。
下面,通过对源码的分析,来实际看下 netty 中这 ChannelFuture 和 ChannelPromise 是如何运用的?因为 netty 中对于IO的操作都是异步的,所以对于这两个接口的运用理解的重要性不言而喻。
Main:
// Start the server.
ChannelFuture f = b.bind(PORT).sync();
main 中 sync()在等待,等待什么呢?先放在这里。继续看bind() 调用,即 doBind(),先大致看下结构,然后我们立即进入 initAndRegister()方法。下面这段代码先不用看细节。
// 最终到这里
private ChannelFuture doBind(final SocketAddress localAddress) {
// initAndRegister() 我们熟悉,就是上文讲 netty 反射工厂使用的时候,追踪过。
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.isDone()) {
...
return promise;
} else {
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
// 注意下面这行
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Throwable cause = future.cause();
if (cause != null) {
} else {
promise.registered();
doBind0(regFuture, channel, localAddress, promise);
}
}
});
return promise;
}
}
initAndRegister() 方法如下。register 的含义就是将 channel 注册到 Selector上。register 异步注册channel,所以 initAndRegister() 会直接返回。
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel();
init(channel);
}
...
// 执行异步注册,实际上 register 调用栈中会新启动一个线程来执行,所以这一句就可以直接返回 ChannelFuture 了。
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
而在 register() 的执行调用栈中,会创建 DefaultChannelPromise 对象作为 regFuture 返回。
关键来了:
返回到的 doBind() 方法中,有一句:regFuture.addListener(),也就是为该 Future 添加了监听方法。那么该方法什么时候会被调用呢?
我们再回到 register() 的调用栈中,其中:
// 该方法是在一个新的线程中调用的
private void register0(ChannelPromise promise) {
...
boolean firstRegistration = neverRegistered;
doRegister();
neverRegistered = false;
registered = true;
pipeline.invokeHandlerAddedIfNeeded();
// 关键一句,执行完注册后,会设置 promise 的状态
safeSetSuccess(promise);
设置的方法中,最终就会通知该 Future 对象的监听方法执行:
// 属于 DefaultChannelPromise 对象的一步操作
private static void notifyListener0(Future future, GenericFutureListener l) {
try {
l.operationComplete(future);
}
}
而 l.operationComplete() 就是 doBind() 方法中:
regFuture.addListener(new ChannelFutureListener() {
// 就是这个方法被调用了。
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Throwable cause = future.cause();
if (cause != null) {
} else {
promise.registered();
doBind0(regFuture, channel, localAddress, promise);
}
}
});
当这个方法被调用时,如果发现 Future 的执行结果正确,那么就会执行 doBind0() 方法,进行 register 后的 bind 操作。
另外,我们会发现,上述过程中,传递的是 DefaultChannelPromise,但是 doBind() 方法中却是用 ChannelFuture 接收的。这样是正常的,因为 DefaultChannelPromise 实现了 ChannelPromise ,而 ChannelPromise 同时继承自 ChannelFuture 和 Promise。但是 ChannelFuture 是不能设置状态的,只能读取。而 Promise 可以在后续的方法中继续设置状态,二者配合,也就实现了上述代码体现的联动。
最后
我们再一次来看一下 doBind() 方法:
// 注意该方法的返回类型是: ChannelFuture
private ChannelFuture doBind(final SocketAddress localAddress) {
...
if (regFuture.isDone()) {
...
return promise;
} else {
// 注意这行,又是一个 Promise,但是我们已经很清楚了.
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Throwable cause = future.cause();
if (cause != null) {
} else {
promise.registered();
// register后的下一步是bind,注意这里把 promise 对象传递进去了。
doBind0(regFuture, channel, localAddress, promise);
}
}
});
// 这里返回了 promise,但是实际上 doBind 的方法返回类型是用 ChannelFuture 接收的。
return promise;
}
}
结合上面代码,这里应该很清晰了,doBind0后续的操作中,对于 promise 的应用也类似,可以自行分析。