netty基础:Future和Promise

2,986 阅读10分钟

上两篇文章 java基础:Future简介java基础:Future原理 介绍了JDK的Future的功能和基本原理,可以发现JDK的Future只能通过get方法同步阻塞获取结果,能不能异步的获取结果呢?也就是,能不能不阻塞调用线程(主线程),当被调用线程(业务线程)执行完结果,将结果回调给需要结果的代码?JDK的Future只能抛出ExecutionException,能不能直接获得任务执行异常的原因?下面依次介绍分析。

先用一句话总结下JDK Future的原理:相当于将要执行的任务封装成一个FutureTask,将该任务放入线程池执行的时候,执行的其实是FutureTask的run方法,FutureTask的run方法执行完任务之后,设置任务的执行结果。这样主线程和业务线程相当于通过FutureTask对象建立了通信渠道,这样主线程就能获得业务线程的执行结果了。显然主线程需要被阻塞等待在业务线程执行的任务执行完。

然而,Netty的Future完全不是这样的。虽然它也有PromiseTask,但是却不是一个公有类,仅仅是内部使用。Netty的Future完全通过监听器的方式获得任务执行的结果或者异常。那么Netty怎么设置结果呢?JDK有FutureTask可以在线程间传递结果。Netty通过什么?Netty通过Promise编码的方式设置结果,并回调监听器。

一、Netty的Future

1. Future

netty的Future接口直接继承自JDK的Future,增加了部分功能:

/**
 * The result of an asynchronous operation.
 */
 // 继承自JDK的Future
@SuppressWarnings("ClassNameSameAsAncestorName")
public interface Future<V> extends java.util.concurrent.Future<V> {

    // 忽略一些简单方法,可查看源码

    /**
     * Returns the cause of the failed I/O operation if the I/O operation has
     * failed.
     *
     * @return the cause of the failure.
     *         {@code null} if succeeded or this future is not
     *         completed yet.
     */
    Throwable cause();

    /**
     * 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);

    Future<V> addListeners(GenericFutureListener<? extends Future<? super V>>... listeners);

    /**
     * Removes the first occurrence of the specified listener from this future.
     * The specified listener is no longer notified when this
     * future is {@linkplain #isDone() done}.  If the specified
     * listener is not associated with this future, this method
     * does nothing and returns silently.
     */
    Future<V> removeListener(GenericFutureListener<? extends Future<? super V>> listener);

    Future<V> removeListeners(GenericFutureListener<? extends Future<? super V>>... listeners);

    /**
     * Waits for this future until it is done, and rethrows the cause of the failure if this future
     * failed.
     */
    Future<V> sync() throws InterruptedException;

    Future<V> syncUninterruptibly();
    /**
     * Waits for this future to be completed.
     *
     * @throws InterruptedException
     *         if the current thread was interrupted
     */
    Future<V> await() throws InterruptedException;

    /**
     * Waits for this future to be completed without
     * interruption.  This method catches an {@link InterruptedException} and
     * discards it silently.
     */
    Future<V> awaitUninterruptibly();

    /**
     * Return the result without blocking. If the future is not done yet this will return {@code null}.
     *
     * As it is possible that a {@code null} value is used to mark the future as successful you also need to check
     * if the future is really done with {@link #isDone()} and not relay on the returned {@code null} value.
     */
    V getNow();

    /**
     * {@inheritDoc}
     *
     * If the cancellation was successful it will fail the future with an {@link CancellationException}.
     */
    @Override
    boolean cancel(boolean mayInterruptIfRunning);
}

这里面忽略了一些方法,只罗列了会详细讨论的方法。

可以看到Netty的Future增加了监听器模式的Listener,方便回调;Future可以通过cause方法获取任务执行异常的原因;主线程也可以通过sync()或者await()方法阻塞等待异步任务执行结束。

2. ChannelFuture

Netty是一个网络框架,肯定要为网络channel建立联系,ChannelFuture就是做这个事情的。

/**
 * The result of an asynchronous {@link Channel} I/O operation.
 * <p>
 * All I/O operations in Netty are asynchronous.  It means any I/O calls will
 * return immediately with no guarantee that the requested I/O operation has
 * been completed at the end of the call.  Instead, you will be returned with
 * a {@link ChannelFuture} instance which gives you the information about the
 * result or status of the I/O operation.
 * <p>
 * A {@link ChannelFuture} is either <em>uncompleted</em> or <em>completed</em>.
 * When an I/O operation begins, a new future object is created.  The new future
 * is uncompleted initially - it is neither succeeded, failed, nor cancelled
 * because the I/O operation is not finished yet.  If the I/O operation is
 * finished either successfully, with failure, or by cancellation, the future is
 * marked as completed with more specific information, such as the cause of the
 * failure.  Please note that even failure and cancellation belong to the
 * completed state.
 * <pre>
 *                                      +---------------------------+
 *                                      | Completed successfully    |
 *                                      +---------------------------+
 *                                 +---->      isDone() = true      |
 * +--------------------------+    |    |   isSuccess() = true      |
 * |        Uncompleted       |    |    +===========================+
 * +--------------------------+    |    | Completed with failure    |
 * |      isDone() = false    |    |    +---------------------------+
 * |   isSuccess() = false    |----+---->      isDone() = true      |
 * | isCancelled() = false    |    |    |       cause() = non-null  |
 * |       cause() = null     |    |    +===========================+
 * +--------------------------+    |    | Completed by cancellation |
 *                                 |    +---------------------------+
 *                                 +---->      isDone() = true      |
 *                                      | isCancelled() = true      |
 *                                      +---------------------------+
 * </pre>
 *
 * Various methods are provided to let you check if the I/O operation has been
 * completed, wait for the completion, and retrieve the result of the I/O
 * operation. It also allows you to add {@link ChannelFutureListener}s so you
 * can get notified when the I/O operation is completed.
 *
 * <h3>Prefer {@link #addListener(GenericFutureListener)} to {@link #await()}</h3>
 *
 * It is recommended to prefer {@link #addListener(GenericFutureListener)} to
 * {@link #await()} wherever possible to get notified when an I/O operation is
 * done and to do any follow-up tasks.
 * <p>
 * {@link #addListener(GenericFutureListener)} is non-blocking.  It simply adds
 * the specified {@link ChannelFutureListener} to the {@link ChannelFuture}, and
 * I/O thread will notify the listeners when the I/O operation associated with
 * the future is done.  {@link ChannelFutureListener} yields the best
 * performance and resource utilization because it does not block at all, but
 * it could be tricky to implement a sequential logic if you are not used to
 * event-driven programming.
 * <p>
 * By contrast, {@link #await()} is a blocking operation.  Once called, the
 * caller thread blocks until the operation is done.  It is easier to implement
 * a sequential logic with {@link #await()}, but the caller thread blocks
 * unnecessarily until the I/O operation is done and there's relatively
 * expensive cost of inter-thread notification.  Moreover, there's a chance of
 * dead lock in a particular circumstance, which is described below.
 *
 * <h3>Do not call {@link #await()} inside {@link ChannelHandler}</h3>
 * <p>
 * The event handler methods in {@link ChannelHandler} are usually called by
 * an I/O thread.  If {@link #await()} is called by an event handler
 * method, which is called by the I/O thread, the I/O operation it is waiting
 * for might never complete because {@link #await()} can block the I/O
 * operation it is waiting for, which is a dead lock.
 * <pre>
 * // BAD - NEVER DO THIS
 * {@code @Override}
 * public void channelRead({@link ChannelHandlerContext} ctx, Object msg) {
 *     {@link ChannelFuture} future = ctx.channel().close();
 *     future.awaitUninterruptibly();
 *     // Perform post-closure operation
 *     // ...
 * }
 *
 * // GOOD
 * {@code @Override}
 * public void channelRead({@link ChannelHandlerContext} ctx, Object msg) {
 *     {@link ChannelFuture} future = ctx.channel().close();
 *     future.addListener(new {@link ChannelFutureListener}() {
 *         public void operationComplete({@link ChannelFuture} future) {
 *             // Perform post-closure operation
 *             // ...
 *         }
 *     });
 * }
 * </pre>
 * <p>
 * In spite of the disadvantages mentioned above, there are certainly the cases
 * where it is more convenient to call {@link #await()}. In such a case, please
 * make sure you do not call {@link #await()} in an I/O thread.  Otherwise,
 * {@link BlockingOperationException} will be raised to prevent a dead lock.
 *
 * <h3>Do not confuse I/O timeout and await timeout</h3>
 *
 * The timeout value you specify with {@link #await(long)},
 * {@link #await(long, TimeUnit)}, {@link #awaitUninterruptibly(long)}, or
 * {@link #awaitUninterruptibly(long, TimeUnit)} are not related with I/O
 * timeout at all.  If an I/O operation times out, the future will be marked as
 * 'completed with failure,' as depicted in the diagram above.  For example,
 * connect timeout should be configured via a transport-specific option:
 * <pre>
 * // BAD - NEVER DO THIS
 * {@link Bootstrap} b = ...;
 * {@link ChannelFuture} f = b.connect(...);
 * f.awaitUninterruptibly(10, TimeUnit.SECONDS);
 * if (f.isCancelled()) {
 *     // Connection attempt cancelled by user
 * } else if (!f.isSuccess()) {
 *     // You might get a NullPointerException here because the future
 *     // might not be completed yet.
 *     f.cause().printStackTrace();
 * } else {
 *     // Connection established successfully
 * }
 *
 * // GOOD
 * {@link Bootstrap} b = ...;
 * // Configure the connect timeout option.
 * <b>b.option({@link ChannelOption}.CONNECT_TIMEOUT_MILLIS, 10000);</b>
 * {@link ChannelFuture} f = b.connect(...);
 * f.awaitUninterruptibly();
 *
 * // Now we are sure the future is completed.
 * assert f.isDone();
 *
 * if (f.isCancelled()) {
 *     // Connection attempt cancelled by user
 * } else if (!f.isSuccess()) {
 *     f.cause().printStackTrace();
 * } else {
 *     // Connection established successfully
 * }
 * </pre>
 */
public interface ChannelFuture extends Future<Void> {

    /**
     * Returns a channel where the I/O operation associated with this
     * future takes place.
     */
    Channel channel();
    // 忽略部分API
}

Netty注释的优秀程度与JDK注释有得一拼,都很棒。

Netty的网络IO都是异步的。也就是说,所有对网络IO的操作都是直接返回的,而不等待IO操作是否完成。那么怎么获得IO操作的结果或者状态呢?可以通过IO操作返回的ChannelFuture实例获得IO操作的结果或者状态。

一个ChannelFuture要么是completed要么是uncompleted,对应着isDone()返回true还是false。一个IO操作开始,一个新的ChannelFuture实例就会被创建。这个Future被初始化为uncompleted,既不是成功、失败,也不是被取消。IO操作一旦成功、失败、被取消,那么这个Future就是completed状态。

Netty建议使用Listener方式而不是wait方式。Listener方式完全是异步的。而wait方式会阻塞主线程直到IO操作完成。比较有意思的是,每次创建Netty的Future都需要传递一个EventExecutor,其实这个EventExecutor就是回调Listener的线程。也就是IO操作不一定要在EventExecutor这个线程中,但是最终的回调肯定是在EventExecutor线程中(回调时进行了线程切换)。

注释中也举了2个例子:

  • 千万不要在IO线程中调用wait方法,有可能会死锁。因为,IO操作在IO线程中执行,而IO线程又在等待IO操作完成。

  • 不要混淆IO操作的超时和await的超时。有可能await超时了,但是IO操作还没有完成。

二、Netty的Promise

1. Promise

通过Future可以获得IO操作的结果,那么怎么设置IO操作的结果呢?就是通过Promise。

/**
 * Special {@link Future} which is writable.
 */
public interface Promise<V> extends Future<V> {

    // 忽略一些API
    /**
     * 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继承自Netty的Future。

2. ChannelPromise

ChannelPromise跟channel建立了联系。

/**
 * Special {@link ChannelFuture} which is writable.
 */
public interface ChannelPromise extends ChannelFuture, Promise<Void> {

    @Override
    Channel channel();
}

三、Netty的Promise实现:DefaultPromise

1. 构造方法

/**
 * Creates a new instance.
 *
 * It is preferable to use {@link EventExecutor#newPromise()} to create a new promise
 *
 * @param executor
 *        the {@link EventExecutor} which is used to notify the promise once it is complete.
 *        It is assumed this executor will protect against {@link StackOverflowError} exceptions.
 *        The executor may be used to avoid {@link StackOverflowError} by executing a {@link Runnable} if the stack
 *        depth exceeds a threshold.
 *
 */
public DefaultPromise(EventExecutor executor) {
    this.executor = checkNotNull(executor, "executor");
}

如注释所说,参数EventExecutor是用来回调Listener的。而且这个通知回调避免了StackOverflowError,其实就是看当前调用栈的深度有多大(连续嵌套了多少个方法),如果调用栈太深的话,就不回调了!当然,这个要看这个任务执行在哪个线程中。如果任务就不是在EventExecutor中执行的,自然就不存在调用栈深度的问题了,因为回调进行了线程切换。

2. setSuccess

@Override
public Promise<V> setSuccess(V result) {
    if (setSuccess0(result)) {
        notifyListeners();
        return this;
    }
    // 这里抛出了异常
    throw new IllegalStateException("complete already: " + this);
}

@Override
public boolean trySuccess(V result) {
    if (setSuccess0(result)) {
        notifyListeners();
        return true;
    }
    // 这里返回了false
    return false;
}

private boolean setSuccess0(V result) {
    // private static final Object SUCCESS = new Object();
    return setValue0(result == null ? SUCCESS : result);
}

private boolean setValue0(Object objResult) {
    // 判断当前状态
    if (RESULT_UPDATER.compareAndSet(this, null, objResult) ||
        RESULT_UPDATER.compareAndSet(this, UNCANCELLABLE, objResult)) {
        // 如果有wait,这里就会进行通知了,也就是因为sync或await调用阻塞的线程就会被唤醒。
        // 注意这时还没有回调Listener呢。
        checkNotifyWaiters();
        return true;
    }
    return false;
}

private void notifyListeners() {
    EventExecutor executor = executor();
    // 检查当前线程(也就是执行setSuccess()方法的线程)是不是在构造方法传进来的EventExecutor中
    // 也就是,Listener肯定是在EventExecutor线程中被回调的
    if (executor.inEventLoop()) {
        final InternalThreadLocalMap threadLocals = InternalThreadLocalMap.get();
        final int stackDepth = threadLocals.futureListenerStackDepth();
        // 防止栈溢出,也没什么玄机,就是不通知Listener了。
        if (stackDepth < MAX_LISTENER_STACK_DEPTH) {
            threadLocals.setFutureListenerStackDepth(stackDepth + 1);
            try {
                notifyListenersNow();
            } finally {
                threadLocals.setFutureListenerStackDepth(stackDepth);
            }
            return;
        }
    }

    safeExecute(executor, new Runnable() {
        @Override
        public void run() {
            notifyListenersNow();
        }
    });
}

3. get

get方法一般在Listener中进行。

@Override
public V get() throws InterruptedException, ExecutionException {
    // 等待完成
    await();

    // 是否异常
    Throwable cause = cause();
    if (cause == null) {
        // 获取结果
        return getNow();
    }
    if (cause instanceof CancellationException) {
        throw (CancellationException) cause;
    }
    throw new ExecutionException(cause);
}

@Override
public boolean isDone() {
    return isDone0(result);
}

private static boolean isDone0(Object result) {
    // 假设上面执行了setSuccess方法
    return result != null && result != UNCANCELLABLE;
}

@Override
public Throwable cause() {
    Object result = this.result;
    return (result instanceof CauseHolder) ? ((CauseHolder) result).cause : null;
}

@SuppressWarnings("unchecked")
@Override
public V getNow() {
    Object result = this.result;
    if (result instanceof CauseHolder || result == SUCCESS || result == UNCANCELLABLE) {
        return null;
    }
    return (V) result;
}

这样通过get方法就获得结果了。可以看到有个getNow方法,可以直接获得结果,而不等待是否执行完。

4. setFailure

@Override
public Promise<V> setFailure(Throwable cause) {
    if (setFailure0(cause)) {
        notifyListeners();
        return this;
    }
    throw new IllegalStateException("complete already: " + this, cause);
}

@Override
public boolean tryFailure(Throwable cause) {
    if (setFailure0(cause)) {
        notifyListeners();
        return true;
    }
    return false;
}

private boolean setFailure0(Throwable cause) {
    return setValue0(new CauseHolder(checkNotNull(cause, "cause")));
}

看完了setSuccess,setFailure就很简单了。cause()方法也很简单了。

5. sync

@Override
public Promise<V> sync() throws InterruptedException {
    await();
    rethrowIfFailed();
    return this;
}

@Override
public Promise<V> syncUninterruptibly() {
    // 
    awaitUninterruptibly();
    // 抛出异常
    rethrowIfFailed();
    return this;
}

private void rethrowIfFailed() {
    Throwable cause = cause();
    if (cause == null) {
        return;
    }

    PlatformDependent.throwException(cause);
}

可以看到sync里面调用的就是await方法。然后又判断了一下是否需要抛出异常。

6. await

@Override
public Promise<V> await() throws InterruptedException {
    if (isDone()) {
        return this;
    }
    // 如果被中断,继续抛出InterruptedException异常
    if (Thread.interrupted()) {
        throw new InterruptedException(toString());
    }
    checkDeadLock();

    synchronized (this) {
        while (!isDone()) {
            incWaiters();
            try {
                wait();
            } finally {
                decWaiters();
            }
        }
    }
    return this;
}

// 一直没有抛出InterruptedException
@Override
public Promise<V> awaitUninterruptibly() {
    if (isDone()) {
        return this;
    }

    checkDeadLock();

    boolean interrupted = false;
    synchronized (this) {
        while (!isDone()) {
            incWaiters();
            try {
                wait();
            } catch (InterruptedException e) {
                // Interrupted while waiting.
                interrupted = true;
            } finally {
                decWaiters();
            }
        }
    }

    if (interrupted) {
        Thread.currentThread().interrupt();
    }

    return this;
}

四、测试代码

public class NettyFutureTest {

    public static void main(String[] args) {
        System.out.println("current thread: " + Thread.currentThread().isDaemon());

        EventExecutor eventExecutor = new DefaultEventExecutor(new DefaultThreadFactory("promise", true));
        Promise<String> promise = eventExecutor.newPromise();
        promise.addListener(new GenericFutureListener<Future<? super String>>() {
            @Override
            public void operationComplete(Future<? super String> future) throws Exception {
                System.out.println("complete....");
                if (future.isSuccess()) {
                    System.out.println("future is success, result: " + future.get());
                } else {
                    System.out.println("future is failed, result: " + future.cause());
                }
            }
        });

        Executors.newSingleThreadExecutor(new DefaultThreadFactory("biz", true)).execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("task is running...");
                sleep(800);
                if (promise.isCancelled()) {
                    System.out.println("promise has been canceled...");
                } else {
                    promise.setSuccess("ok");
                }
                System.out.println("biz execute over...");
            }
        });
//        eventExecutor.execute(new Runnable() {
//            @Override
//            public void run() {
//                System.out.println("task is running...");
//                sleep(800);
//                if (promise.isCancelled()) {
//                    System.out.println("promise has been canceled...");
//                } else {
//                    promise.setSuccess("ok");
//                }
//                System.out.println("biz execute over...");
//            }
//        });

//        sleep(100);
//        boolean canceled = promise.cancel(true);
//        System.out.println("promise cancel result: " + canceled);

//        Thread.currentThread().interrupt();
//        System.out.println("main thread isInterrupted: " + Thread.currentThread().isInterrupted());

        try {
            promise.sync();
        } catch (InterruptedException e) {
            System.out.println("here...");
            e.printStackTrace();
        }


        System.out.println("main over...");

    }

    private static void sleep(long timeMs) {
        try {
            TimeUnit.MILLISECONDS.sleep(timeMs);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

上面代码的执行结果:

// main线程不是daemon线程
current thread: false
// 业务开始执行
task is running...
// 可以发现setSuccess之后,main线程就被唤醒退出了
main over...
// 业务执行结束
biz execute over...
// 在EventExecutor中回调Listener
// 其实我总觉着,后面两个可能不会输出。
// 因为main线程都退出了,而EventExecutor是个daemon线程,所以可能调度不上。
complete....
future is success, result: ok

再说一句,DefaultEventExecutor的run方法是一个死循环。

上面的测试程序,有很多被注释的代码。可以分别执行执行试试。

硬广告

欢迎关注公众号:double6