本文最后更新于:April 11, 2022 pm
Netty 源码分析系列:
Netty 源码解析(一): 开始
Netty 源码解析(二): Netty 的 Channel
Netty 源码解析(三): Netty 的 Future 和 Promise
Netty 源码解析(四): Netty 的 ChannelPipeline
Netty 源码解析(五): Netty 的线程池分析
Netty 源码解析(六): Channel 的 register 操作
Netty 源码解析(七): NioEventLoop 工作流程
Netty 源码解析(八): 回到 Channel 的 register 操作
Netty 源码解析(九): connect 过程和 bind 过程分析
目录
Netty 中的异步编程: Future 和 Promise
Netty 中非常多的异步调用,所以在介绍更多 NIO 相关的内容之前,我们来看看它的异步接口是怎么使用的。
前面我们在介绍 Echo 例子的时候,已经用过了 ChannelFuture 这个接口了:
争取在看完本节后,读者能搞清楚上面的这几行划线部分是怎么走的。
关于 Future 接口,我想大家应该都很熟悉,用得最多的就是在使用 Java 的线程池 ThreadPoolExecutor 的时候了。在 submit 一个任务到线程池中的时候,返回的就是一个 Future 实例,通过它来获取提交的任务的执行状态和最终的执行结果,我们最常用它的 isDone()
和 get()
方法。
下面是 JDK 中的 Future 接口 java.util.concurrent.Future:
| public interface Future<V> { boolean cancel(boolean mayInterruptIfRunning); boolean isCancelled(); boolean isDone(); V get() throws InterruptedException, ExecutionException; V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }
|
Netty 中的 Future 接口(同名)继承了 JDK 中的 Future 接口,然后添加了一些方法:
// io.netty.util.concurrent.Future
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| public interface Future<V> extends java.util.concurrent.Future<V> {
boolean isSuccess(); boolean isCancellable();
Throwable cause();
Future<V> addListener(GenericFutureListener<? extends Future<? super V>> listener); Future<V> addListeners(GenericFutureListener<? extends Future<? super V>>... listeners);
Future<V> removeListener(GenericFutureListener<? extends Future<? super V>> listener); Future<V> removeListeners(GenericFutureListener<? extends Future<? super V>>... listeners);
Future<V> sync() throws InterruptedException; Future<V> syncUninterruptibly();
Future<V> await() throws InterruptedException; Future<V> awaitUninterruptibly(); boolean await(long timeout, TimeUnit unit) throws InterruptedException; boolean await(long timeoutMillis) throws InterruptedException; boolean awaitUninterruptibly(long timeout, TimeUnit unit); boolean awaitUninterruptibly(long timeoutMillis);
V getNow();
@Override boolean cancel(boolean mayInterruptIfRunning); }
|
看完上面的 Netty 的 Future 接口,我们可以发现,它加了 sync() 和 await() 用于阻塞等待,还加了 Listeners,只要任务结束去回调 Listener 们就可以了,那么我们就不一定要主动调用 isDone() 来获取状态,或通过 get() 阻塞方法来获取值。
所以它其实有两种使用范式
顺便说下 sync() 和 await() 的区别:sync() 内部会先调用 await() 方法,等 await() 方法返回后,会检查下这个任务是否失败,如果失败,重新将导致失败的异常抛出来。也就是说,如果使用 await(),任务抛出异常后,await() 方法会返回,但是不会抛出异常,而 sync() 方法返回的同时会抛出异常。
我们也可以看到,Future 接口没有和 IO 操作关联在一起,还是比较纯净的接口。
接下来,我们来看 Future 接口的子接口 ChannelFuture,这个接口用得最多,它将和 IO 操作中的 Channel 关联在一起了,用于异步处理 Channel 中的事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public interface ChannelFuture extends Future<Void> {
Channel channel();
@Override ChannelFuture addListener(GenericFutureListener<? extends Future<? super Void>> listener); @Override ChannelFuture addListeners(GenericFutureListener<? extends Future<? super Void>>... listeners); @Override ChannelFuture removeListener(GenericFutureListener<? extends Future<? super Void>> listener); @Override ChannelFuture removeListeners(GenericFutureListener<? extends Future<? super Void>>... listeners);
@Override ChannelFuture sync() throws InterruptedException; @Override ChannelFuture syncUninterruptibly(); @Override ChannelFuture await() throws InterruptedException; @Override ChannelFuture awaitUninterruptibly();
boolean isVoid(); }
|
我们看到,ChannelFuture 接口相对于 Future 接口,除了将 channel 关联进来,没有增加什么东西。还有个 isVoid() 方法算是不那么重要的存在吧。其他几个都是方法覆写,为了让返回值类型变为 ChannelFuture,而不是原来的 Future。
这里有点跳,我们来介绍下 Promise 接口,它和 ChannelFuture 接口无关,而是和前面的 Future 接口相关,Promise 这个接口非常重要。
Promise 接口和 ChannelFuture 一样,也继承了 Netty 的 Future 接口,然后加了一些 Promise 的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| 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();
@Override Promise<V> addListener(GenericFutureListener<? extends Future<? super V>> listener); @Override Promise<V> addListeners(GenericFutureListener<? extends Future<? super V>>... listeners);
@Override Promise<V> removeListener(GenericFutureListener<? extends Future<? super V>> listener); @Override Promise<V> removeListeners(GenericFutureListener<? extends Future<? super V>>... listeners);
@Override Promise<V> await() throws InterruptedException; @Override Promise<V> awaitUninterruptibly();
@Override Promise<V> sync() throws InterruptedException; @Override Promise<V> syncUninterruptibly(); }
|
可能有些读者对 Promise 的概念不是很熟悉,这里简单说两句。
我觉得只要明白一点,Promise 实例内部是一个任务,任务的执行往往是异步的,通常是一个线程池来处理任务。Promise 提供的 setSuccess(V result) 或 setFailure(Throwable t) 将来会被某个执行任务的线程在执行完成以后调用,同时那个线程在调用 setSuccess(result) 或 setFailure(t) 后会回调 listeners 的回调函数(当然,回调的具体内容不一定要由执行任务的线程自己来执行,它可以创建新的线程来执行,也可以将回调任务提交到某个线程池来执行)。而且,一旦 setSuccess(…) 或 setFailure(…) 后,那些 await() 或 sync() 的线程就会从等待中返回。
所以这里就有两种编程方式,一种是用 await(),等 await() 方法返回后,得到 promise 的执行结果,然后处理它;另一种就是提供 Listener 实例,我们不太关心任务什么时候会执行完,只要它执行完了以后会去执行 listener 中的处理方法就行。
接下来,我们再来看下 ChannelPromise,它继承了前面介绍的 ChannelFuture 和 Promise 接口。
ChannelPromise 接口在 Netty 中使用得比较多,因为它综合了 ChannelFuture 和 Promise 两个接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
public interface ChannelPromise extends ChannelFuture, Promise<Void> {
@Override Channel channel();
@Override ChannelPromise setSuccess(Void result); ChannelPromise setSuccess(); boolean trySuccess(); @Override ChannelPromise setFailure(Throwable cause);
@Override ChannelPromise addListener(GenericFutureListener<? extends Future<? super Void>> listener); @Override ChannelPromise addListeners(GenericFutureListener<? extends Future<? super Void>>... listeners); @Override ChannelPromise removeListener(GenericFutureListener<? extends Future<? super Void>> listener); @Override ChannelPromise removeListeners(GenericFutureListener<? extends Future<? super Void>>... listeners);
@Override ChannelPromise sync() throws InterruptedException; @Override ChannelPromise syncUninterruptibly(); @Override ChannelPromise await() throws InterruptedException; @Override ChannelPromise awaitUninterruptibly();
ChannelPromise unvoid(); }
|
我们可以看到,它综合了 ChannelFuture 和 Promise 中的方法,只不过通过覆写将返回值都变为 ChannelPromise 了而已,没有增加什么新的功能。
小结一下,我们上面介绍了几个接口,Future 以及它的子接口 ChannelFuture 和 Promise,然后是 ChannelPromise 接口同时继承了 ChannelFuture 和 Promise。
我把这几个接口的主要方法列在一起,这样大家看得清晰些:
接下来,我们需要来一个实现类,这样才能比较直观地看出它们是怎么使用的,因为上面的这些都是接口定义,具体还得看实现类是怎么工作的。
下面,我们来介绍下 DefaultPromise 这个实现类,这个类很常用,它的源码也不短,我们先介绍几个关键的内容,然后介绍一个示例使用。
首先,我们看下它有哪些属性:
| public class DefaultPromise<V> extends AbstractFuture<V> implements Promise<V> { private volatile Object result; private final EventExecutor executor; private Object listeners;
private short waiters;
private boolean notifyingListeners; ...... }
|
可以看出,此类实现了 Promise,但是没有实现 ChannelFuture,所以它和 Channel 联系不起来。
别急,我们后面会碰到另一个类 DefaultChannelPromise 的使用,这个类是综合了 ChannelFuture 和 Promise 的,但是它的实现其实大部分都是继承自这里的 DefaultPromise 类的。
说完上面的属性以后,大家可以看下 setSuccess(V result)
、trySuccess(V result)
和 setFailure(Throwable cause)
、 tryFailure(Throwable cause)
这几个方法:
看出 setSuccess(result) 和 trySuccess(result) 的区别了吗?
上面几个方法都非常简单,先设置好值,然后执行监听者们的回调方法。notifyListeners() 方法感兴趣的读者也可以看一看,不过它还涉及到 Netty 线程池的一些内容,我们还没有介绍到线程池,这里就不展开了。上面的代码,在 setSuccess0 或 setFailure0 方法中都会唤醒阻塞在 sync() 或 await() 的线程
另外,就是可以看下 sync() 和 await() 的区别,其他的我觉得随便看看就好了。
| @Override public Promise<V> sync() throws InterruptedException { await(); rethrowIfFailed(); return this; }
|
接下来,我们来写个实例代码吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| public static void main(String[] args) {
EventExecutor executor = new DefaultEventExecutor();
Promise promise = new DefaultPromise(executor);
promise.addListener(new GenericFutureListener<Future<Integer>>() { @Override public void operationComplete(Future future) throws Exception { if (future.isSuccess()) { System.out.println("任务结束,结果:" + future.get()); } else { System.out.println("任务失败,异常:" + future.cause()); } } }).addListener(new GenericFutureListener<Future<Integer>>() { @Override public void operationComplete(Future future) throws Exception { System.out.println("任务结束,balabala..."); } });
executor.submit(new Runnable() { @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { } promise.setSuccess(123456); } });
try { promise.sync(); } catch (InterruptedException e) { } }
|
运行代码,两个 listener 将在 5 秒后将输出:
| 任务结束,结果:123456 任务结束,balabala...
|
读者这里可以试一下 sync() 和 await() 的区别,在任务中调用 promise.setFailure(new RuntimeException()) 试试看。
上面的代码中,大家可能会对线程池 executor 和 promise 之间的关系感到有点迷惑。读者应该也要清楚,具体的任务不一定就要在这个 executor 中被执行。任务结束以后,需要调用 promise.setSuccess(result) 作为通知。
通常来说,promise 代表的 future 是不需要和线程池搅在一起的,future 只关心任务是否结束以及任务的执行结果,至于是哪个线程或哪个线程池执行的任务,future 其实是不关心的。
不过 Netty 毕竟不是要创建一个通用的线程池实现,而是和它要处理的 IO 息息相关的,所以我们只不过要理解它就好了。
这节就说这么多吧,我们回过头来再看一下这张图,看看大家是不是看懂了这节内容:
我们就说说上图左边的部分吧,虽然我们还不知道 bind() 操作中具体会做什么工作,但是我们应该可以猜出一二。
显然,main 线程调用 b.bind(port) 这个方法会返回一个 ChannelFuture,bind() 是一个异步方法,当某个执行线程执行了真正的绑定操作后,那个执行线程一定会标记这个 future 为成功(我们假定 bind 会成功),然后这里的 sync() 方法(main 线程)就会返回了。
如果 bind(port) 失败,我们知道,sync() 方法会将异常抛出来,然后就会执行到 finally 块了。
一旦绑定端口 bind 成功,进入下面一行,f.channel() 方法会返回该 future 关联的 channel。
channel.closeFuture() 也会返回一个 ChannelFuture,然后调用了 sync() 方法,这个 sync() 方法返回的条件是:有其他的线程关闭了 NioServerSocketChannel,往往是因为需要停掉服务了,然后那个线程会设置 future 的状态( setSuccess(result) 或 setFailure(cause) ),这个 sync() 方法才会返回。
这节就到这里,希望大家对 Netty 中的异步编程有些了解,后续碰到源码的时候能知道是怎么使用的了。