【Netty权威指南】15-Future和Promise

1、Future功能

Future最早来源于JDK的java.util.concurrent.Future,它用于代表异步操作的结果。相关API如图19-1所示。


可以通过get方法获取操作结果,如果操作尚未完成,则会同步阻塞当前调用的线程;如果不允许阻塞太长时间或者无限期阻塞,可以通过带超时时间的get方法获取结果;如果到达超时时间操作仍然没有完成,则抛出 TimeoutException。

通过 isDone()方法可以判断当前的异步操作是否完成,如果完成,无论成功与否,都返回true,否则返回 false。
通过 cancel可以尝试取消异步操作,它的结果是未知的,如果操作已经完成,或者发生其他未知的原因拒绝取消,取消操作将会失败。

ChannelFuture功能介绍

由于Netty的 Future都是与异步I/O操作相关的,因此,命名为 ChannelFuture,代表它与 Channel操作相关。
它的API接口列表如下所示。

在Netty中,所有的IO操作都是异步的,这意味着任何IO调用都会立即返回,而不是像传统BIO那样同步等待操作完成。异步操作会带来一个问题:调用者如何获取异步操作的结果? ChannelFuture就是为了解决这个问题而专门设计的。下面我们一起看它的原理。
ChannelFuture有两种状态: uncompleted和completed。当开始一个IO操作时,一个新的ChannelFuture被创建,此时它处于uncompleted状态—非失败、非成功、非取消,因为IO操作此时还没有完成。一旦IO操作完成, ChannelFuture将会被设置成completed,它的结果有如下三种可能。
◎操作成功
◎操作失败
◎操作被取消
ChannelFuture的状态迁移图如图19-2所示。

ChannelFuture提供了一系列新的API,用于获取操作结果、添加事件监听器、取消IO操作、同步等待等。
我们重点介绍添加监听器的接口。管理监听器相关的接口定义如图19-3所示

Netty强烈建议直接通过添加监听器的方式获取IO操作结果,或者进行后续的相关操作。
ChannelFuture可以同时增加一个或者多个 GenericFutureListener,也可以用 remove方法删除GenericFutureListener。
GenericFutureListener的接口定义如图19-4所示。

当IO操作完成之后,IO线程会回调 ChannelFuture中 GenericFutureListener的operationComplete方法,并把 Channelfuture对象当作方法的入参。如果用户需要做上下文相关的操作,需要将上下文信息保存到对应的 ChannelFuture中。
推荐通过 GenericFutureListener代替 ChannelFuture的get等方法的原因是:当我们进行异步IO操作时,完成的时间是无法预测的,如果不设置超时时间,它会导致调用线程长时间被阻塞,甚至挂死。而设置超时时间,时间又无法精确预测。利用异步通知机制回调 GenericFutureListener是最佳的解决方案,它的性能最优。
需要注意的是:不要在 ChannelHandler中调用 ChannelFuture的 await方法,这会导致死锁。原因是发起IO操作之后,由IO线程负责异步通知发起IO操作的用户线程,如果IO线程和用户线程是同一个线程,就会导致IO线程等待自己通知操作完成,这就导致了死锁,这跟经典的两个线程互等待死锁不同,属于自己把自己挂死相关代码示例如图19-5所示。

异步I/O操作有两类超时:一个是TCP层面的I/O超时,另一个是业务逻辑层面的操作超时。两者没有必然的联系,但是通常情况下业务逻辑超时时间应该大于IO超时时间它们两者是包含的关系。
相关代码举例如图19-6所示。

需要指出的是: ChannelFuture超时并不代表IO超时,这意味着 ChannelFuture超时后,如果没有关闭连接资源,随后连接依旧可能会成功,这会导致严重的问题。所以通常情况下,必须要考虑究竟是设置IO超时还是 ChannelFuture超时。

2、ChannelFuture源码分析

ChannelFuture的接口继承关系如图19-8所示。

AbstractFuture

AbstractFuture实现 Future接口,它不允许IO操作被取消。下面我们重点看它的代码实现。
获取异步操作结果的代码如图19-9所示。

首先,调用 await()方法进行无限期阻塞,当IO操作完成后会被 notify()。程序继续向下执行,检査IO操作是否发生了异常,如果没有异常,则通过 getNow()方法获取结果并返回。否则,将异常堆栈进行包装,抛出 ExecutionException接着我们看支持超时的获取操作结果方法,如图19-10所示。

支持超时很简单,调用 await(long timeout, TimeUnit unit)方法即可。如果超时,则抛出 TimeoutException。如果没有超时,则依次判断是否发生了IO异常等情况,操作与无参数的get方法相同其他 ChannelFuture的实现子类,由于功能比较简单,读者阅读起来也没太大难度,所以这里不再花费时间进行详细解读,感兴趣的读者可以独立阅读和分析。

3、Promise功能介绍

Promise是可写的 Future, Future自身并没有写操作相关的接口, Netty通过 Promise对 Future进行扩展,用于设置IO操作的结果。 Future相关的接口定义如图19-12所示。

Promise相关的写操作接口定义如图19-13所示。

Netty发起IO操作的时候,会创建一个新的 Promise对象,例如调用 ChannelHandlerContext的write(Object object)方法时,会创建一个新的ChannelPromise,相关代码如图19-14所示。

当IO操作发生异常或者完成时,设置 Promise的结果,代码如图19-15所示。

4、Promise源码分析

4.1、Promise继承关系图

由于I/O操作种类非常多,因此对应的 Promise子类也非常繁多,它的继承关系如图下所示。

 尽管 Promise的子类种类繁多,但是它的功能相对比较清晰,代码也较为简单,因此我们只分析一个它的实现子类的源码,如果读者对其他子类感兴趣,可以自行学习。

4.2、DefaultPromise

下面看比较重要的 setSuccess方法的实现,如图19-17所示。
首先调用 setSuccess()方法并对其操作结果进行判断,如果操作成功,则调用notifyListeners方法通知 listener。

首先判断当前 Promise的操作结果是否已经被设置,如果已经被设置,则不允许重复设置,返回设置失败。
由于可能存在IO线程和用户线程同时操作 Promise,所以设置操作结果的时候需要加锁保护,防止并发操作。
对操作结果是否被设置进行二次判断(为了提升并发性能的二次判断),如果已经被设置,则返回操作失败。
对操作结果 result进行判断,如果为空,说明仅仅需要 notify在等待的业务线程,不包含具体的业务逻辑对象。因此,将 result设置为系统默认的 SUCCESS。如果操作结果非空,将结果设置为 result。
如果有正在等待异步IO操作完成的用户线程或者其他系统线程,则调用 notify方法唤醒所有正在等待的线程。注意, notify和wait方法都必须在同步块内使用。
分析完 setSuccess()方法,我们继续看 await方法的实现,如图19-19所示。

如果当前的 Promise已经被设置,则直接返回。如果线程已经被中断,则抛出中断异常。通过同步关键字锁定当前 Promise对象,使用循环判断对 isDone结果进行判断,进行循环判断的原因是防止线程被意外唤醒导致的功能异常。
由于在IO线程中调用 Promise的 await或者sync方法会导致死锁,所以在循环体中需要对死锁进行保护性校验,防止IO线程被挂死,最后调用 java.lang.Object.wait()方法进行无限期等待,直到IO线程调用 setSuccess方法、trySuccess方法、setFailure或者tryFailure方法。
 

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值