• 欢迎访问 winrains 的个人网站!
  • 本网站主要从互联网整理和收集了与Java、网络安全、Linux等技术相关的文章,供学习和研究使用。如有侵权,请留言告知,谢谢!

Netty 源码解析(3):Netty 的 Future 和 Promise

Netty winrains 来源:javadoop 12个月前 (11-10) 49次浏览

Netty 中的异步编程: Future 和 Promise

Netty 中非常多的异步调用,所以在介绍更多 NIO 相关的内容之前,我们来看看它的异步接口是怎么使用的。
前面我们在介绍 Echo 例子的时候,已经用过了 ChannelFuture 这个接口了:
6
争取在看完本节后,读者能搞清楚上面的这几行划线部分是怎么走的。
关于 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

public interface Future<V> extends java.util.concurrent.Future<V> {
    // 是否成功
    boolean isSuccess();
    // 是否可取消
    boolean isCancellable();
    // 如果任务执行失败,这个方法返回异常信息
    Throwable cause();
    // 添加 Listener 来进行回调
    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;
    // 不响应中断的 sync(),这个大家应该都很熟了
    Future<V> syncUninterruptibly();
    // 阻塞等待任务结束,和 sync() 功能是一样的,不过如果任务失败,它不会抛出执行过程中的异常
    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);
    // 获取执行结果,不阻塞。我们都知道 java.util.concurrent.Future 中的 get() 是阻塞的
    V getNow();
    // 取消任务执行,如果取消成功,任务会因为 CancellationException 异常而导致失败
    //      也就是 isSuccess()==false,同时上面的 cause() 方法返回 CancellationException 的实例。
    // mayInterruptIfRunning 说的是:是否对正在执行该任务的线程进行中断(这样才能停止该任务的执行),
    //       似乎 Netty 中 Future 接口的各个实现类,都没有使用这个参数
    @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 中的事件。

public interface ChannelFuture extends Future<Void> {
    // ChannelFuture 关联的 Channel
    Channel channel();
    // 覆写以下几个方法,使得它们返回值为 ChannelFuture 类型
    @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();
    // 用来标记该 future 是 void 的,
    // 这样就不允许使用 addListener(...), sync(), await() 以及它们的几个重载方法
    boolean isVoid();
}

我们看到,ChannelFuture 接口相对于 Future 接口,除了将 channel 关联进来,没有增加什么东西。还有个 isVoid() 方法算是不那么重要的存在吧。其他几个都是方法覆写,为了让返回值类型变为 ChannelFuture,而不是原来的 Future。
这里有点跳,我们来介绍下 Promise 接口,它和 ChannelFuture 接口无关,而是和前面的 Future 接口相关,Promise 这个接口非常重要。
Promise 接口和 ChannelFuture 一样,也继承了 Netty 的 Future 接口,然后加了一些 Promise 的内容:

public interface Promise<V> extends Future<V> {
    // 标记该 future 成功及设置其执行结果,并且会通知所有的 listeners。
    // 如果该操作失败,将抛出异常(失败指的是该 future 已经有了结果了,成功的结果,或者失败的结果)
    Promise<V> setSuccess(V result);
    // 和 setSuccess 方法一样,只不过如果失败,它不抛异常,返回 false
    boolean trySuccess(V result);
    // 标记该 future 失败,及其失败原因。
    // 如果失败,将抛出异常(失败指的是已经有了结果了)
    Promise<V> setFailure(Throwable cause);
    // 标记该 future 失败,及其失败原因。
    // 如果已经有结果,返回 false,不抛出异常
    boolean tryFailure(Throwable cause);
    // 标记该 future 不可以被取消
    boolean setUncancellable();
    // 这里和 ChannelFuture 一样,对这几个方法进行覆写,目的是为了返回 Promise 类型的实例
    @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 接口。
4
ChannelPromise 接口在 Netty 中使用得比较多,因为它综合了 ChannelFuture 和 Promise 两个接口:

/**
 * Special {@link ChannelFuture} which is writable.
 */
public interface ChannelPromise extends ChannelFuture, Promise<Void> {
    // 覆写 ChannelFuture 中的 channel() 方法,其实这个方法一点没变
    @Override
    Channel channel();
    // 下面几个方法是覆写 Promise 中的接口,为了返回值类型是 ChannelPromise
    @Override
    ChannelPromise setSuccess(Void result);
    ChannelPromise setSuccess();
    boolean trySuccess();
    @Override
    ChannelPromise setFailure(Throwable cause);
    // 到这里大家应该都熟悉了,下面几个方法的覆写也是为了得到 ChannelPromise 类型的实例
    @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();
    /**
     * Returns a new {@link ChannelPromise} if {@link #isVoid()} returns {@code true} otherwise itself.
     */
    // 我们忽略这个方法吧。
    ChannelPromise unvoid();
}

我们可以看到,它综合了 ChannelFuture 和 Promise 中的方法,只不过通过覆写将返回值都变为 ChannelPromise 了而已,没有增加什么新的功能
小结一下,我们上面介绍了几个接口,Future 以及它的子接口 ChannelFuture 和 Promise,然后是 ChannelPromise 接口同时继承了 ChannelFuture 和 Promise。
我把这几个接口的主要方法列在一起,这样大家看得清晰些:
4
接下来,我们需要来一个实现类,这样才能比较直观地看出它们是怎么使用的,因为上面的这些都是接口定义,具体还得看实现类是怎么工作的。
下面,我们来介绍下 DefaultPromise 这个实现类,这个类很常用,它的源码也不短,我们先介绍几个关键的内容,然后介绍一个示例使用。
首先,我们看下它有哪些属性:

public class DefaultPromise<V> extends AbstractFuture<V> implements Promise<V> {
      // 保存执行结果
    private volatile Object result;
    // 执行任务的线程池,promise 持有 executor 的引用,这个其实有点奇怪了
    // 因为“任务”其实没必要知道自己在哪里被执行的
    private final EventExecutor executor;
      // 监听者,回调函数,任务结束后(正常或异常结束)执行
    private Object listeners;
    // 等待这个 promise 的线程数(调用sync()/await()进行等待的线程数量)
    private short waiters;
    // 是否正在唤醒等待线程,用于防止重复执行唤醒,不然会重复执行 listeners 的回调方法
    private boolean notifyingListeners;
    ......
}

可以看出,此类实现了 Promise,但是没有实现 ChannelFuture,所以它和 Channel 联系不起来。
别急,我们后面会碰到另一个类 DefaultChannelPromise 的使用,这个类是综合了 ChannelFuture 和 Promise 的,但是它的实现其实大部分都是继承自这里的 DefaultPromise 类的。

说完上面的属性以后,大家可以看下 setSuccess(V result)trySuccess(V result)setFailure(Throwable cause)tryFailure(Throwable cause) 这几个方法:
8

看出 setSuccess(result) 和 trySuccess(result) 的区别了吗?

上面几个方法都非常简单,先设置好值,然后执行监听者们的回调方法。notifyListeners() 方法感兴趣的读者也可以看一看,不过它还涉及到 Netty 线程池的一些内容,我们还没有介绍到线程池,这里就不展开了。上面的代码,在 setSuccess0 或 setFailure0 方法中都会唤醒阻塞在 sync() 或 await() 的线程
另外,就是可以看下 sync() 和 await() 的区别,其他的我觉得随便看看就好了。

@Override
public Promise<V> sync() throws InterruptedException {
    await();
    // 如果任务是失败的,重新抛出相应的异常
    rethrowIfFailed();
    return this;
}

接下来,我们来写个实例代码吧:

public static void main(String[] args) {
    // 构造线程池
    EventExecutor executor = new DefaultEventExecutor();
    // 创建 DefaultPromise 实例
    Promise promise = new DefaultPromise(executor);
    // 下面给这个 promise 添加两个 listener
    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...");
        }
    });
    // 提交任务到线程池,五秒后执行结束,设置执行 promise 的结果
    executor.submit(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
            }
            // 设置 promise 的结果
            // promise.setFailure(new RuntimeException());
            promise.setSuccess(123456);
        }
    });
    // main 线程阻塞等待执行结果
    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 息息相关的,所以我们只不过要理解它就好了。
这节就说这么多吧,我们回过头来再看一下这张图,看看大家是不是看懂了这节内容:
6
我们就说说上图左边的部分吧,虽然我们还不知道 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 中的异步编程有些了解,后续碰到源码的时候能知道是怎么使用的了。

作者:javadoop

来源:https://www.javadoop.com/post/netty-part-3


版权声明:文末如注明作者和来源,则表示本文系转载,版权为原作者所有 | 本文如有侵权,请及时联系,承诺在收到消息后第一时间删除 | 如转载本文,请注明原文链接。
喜欢 (0)