Netty核心概念(9)之Future

1.前言

 第7节讲解JAVA的线程模型中就说到了Future,并解释了为什么可以主线程可以获得线程池任务的执行后结果,变成一种同步状态。秘密就在于Java将所有的runnable和callable任务,统一变成了callable,最终包装成了FutureTask对象,该类实现了Runnable接口和Future接口,所以FutureTask能够被线程执行。最终异步执行过程全部由该类控制逻辑,所以在get的时候锁住了该类,run方法执行的时候释放了锁,这样就满足了能够在异步线程执行完毕获取相关结果的能力。

 本章介绍一下Netty对Future的设计,Netty的声明就是一个异步事件驱动框架,上一节学习了整个线程调度的过程,并在最后给出了前几节的一个综合流程图,虽然图中提到了几种Future,但是没有具体介绍细节,这些将在本节得到解释。

2.相关概念

2.1 Future

 虽然Java中已经定义了Future,但是满足不了Netty的需求,所以Netty新写了一个Future接口,继承了JDK的Future。额外方法定义如下图:

 接口主要追加了两个功能:1.增加了判断任务是否成功失败的方法,以及失败获取异常信息;2.增加了任务完成时触发的监听器

2.2 Promise

 该类继承自Future,自然是增加了额外的功能了:这是一个可写的Future。什么意思呢?通过之前的知识,我们知道Future都是由异步线程控制的,主线程是无法控制线程执行的。Promise的作用就是主线程能够控制一下执行的任务。

  setSuccess():标记任务成功,并触发所有listener。如果任务早就成功或失败,则抛出异常

  trySuccess():同上,但是失败只是返回false,而不是抛出异常

 这里就解释这两个方法,其他方法是覆盖了父接口的方法,确定返回的具体类型而已。根据方法我们也能大体明白可写的含义了。

3 主要Future详解

3.1 DefaultChannelPromise

 该类是在注册channel时创建的,SingleThreadEventLoop的register方法。和Java的FutureTask不同,FutureTask是作为一个任务交给线程池,在内部控制任务执行。DefaultChannelPromise则是持有了Channel和EventExecutor二者,在外边处理逻辑。其上层有2个抽象父类:

  1.AbstractFuture:

    该类就实现了get方法,原理是调用了await()方法,await之后唤醒肯定就是任务结束了,判断有无异常,最终返回结果还是抛出异常。

  2.DefaultPromise:

    该类提供了一个Promise应该具备的基本实现。对任务标记结果,触发listener等。其主要有个result,对结果进行CAS操作来判断任务是否完成。

    setSuccess过程如下:1.设置成功的结果;2.触发所有的listener。设置结果主要是CAS更新result字段,然后判断是否有get请求等待任务执行完,直接notifyAll即可。触发listener的过程在于先判断当前线程是否是事件线程中,触发方法必须由EventLoop线程执行,然后就是遍历触发listener的operationComplete方法。

    await过程如下:1.判断是否执行完,执行完直接返回;2.判断线程是否中断,中断抛出异常;3.检查死锁,即wait操作不能在EventLoop的线程中执行;4.如果没执行完,等待者计数,然后wait。

 DefaultChannelPromise相对于DefaultPromise而言只是增加了一个channel字段,其它的方法都是调用父类方法。接下来我们看看register过程是怎么使用这个Promise的吧。

    public final void register(EventLoop eventLoop, final ChannelPromise promise) {
            if (eventLoop == null) {
                throw new NullPointerException("eventLoop");
            }
            if (isRegistered()) {
                promise.setFailure(new IllegalStateException("registered to an event loop already"));
                return;
            }
            if (!isCompatible(eventLoop)) {
                promise.setFailure(
                        new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
                return;
            }

            AbstractChannel.this.eventLoop = eventLoop;

            if (eventLoop.inEventLoop()) {
                register0(promise);
            } else {
                try {
                    eventLoop.execute(new Runnable() {
                        @Override
                        public void run() {
                            register0(promise);
                        }
                    });
                } catch (Throwable t) {
                    logger.warn(
                            "Force-closing a channel whose registration task was not accepted by an event loop: {}",
                            AbstractChannel.this, t);
                    closeForcibly();
                    closeFuture.setClosed();
                    safeSetFailure(promise, t);
                }
            }
        }

 可以看到在注册过程中实际上就是使用setXXX方法来处理相关逻辑的,这个和Java的FutureTask采取了不同的方式。

3.2 SucceededFuture

 上面讲了一个Promise控制主线程和线程池的同步状态,那个是依靠promise才有的setXXX接口来触发的。那么Future是怎么控制的呢?答案是Future不需要控制,返回Future的时候就已经有结果了,并且返回一定是一个同步过程。

 以SucceededFuture为例。Bootstrap中的doResolveAndConnect0方法有段:final Future<SocketAddress> resolveFuture = resolver.resolve(remoteAddress);其解析成功就会返回带有结果的SucceededFuture。看这个类的sync方法也和Promise的不一样,Promise是await,Future是直接返回。这个可以说明Future和Promise的区别:Future用于同步任务,Promise用于异步任务。不知道Netty为什么会设计成这样,让人会有些疑惑。但是记住这点,再加上Promise的set方法达成的效果,就可以理解Netty的Future了。

4.总结

 Netty的Future设计采取了和Java的FutureTask不同的设计思路。Java的思路是将Futuren包装成一个任务,这样异步线程执行这个FutureTask的时候,其就可以知道任务的执行状态。Netty将Future扩展成了Promise。Future作为同步方法直接返回的结果类,使用较少。Promise提供了setXXX方法,给异步线程调用该方法告知执行状态。相同的地方在于Promise也必须被异步线程持有,才能使用set方法。

posted @ 2018-05-04 22:18  dark_saber  阅读(2439)  评论(0编辑  收藏  举报