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方法。