Java和netty的异步框架:Future体系

831 阅读6分钟

异步编程在 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:执行中
  • 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 的应用也类似,可以自行分析。