这里主要讨论ChannelHandler的 @ S harable注解的作用。
ChannelHandler 的一端是Netty NIO线程,另一端则是业务线程池,在多线程并发场景下理解ChannelHandler的并发安全性很重要,如果使用不当,会产生性能和并发安全问题。
【非共享ChannelHandler的线程安全问题】
如果每个channel创建某个ChannelHandler时都new一个自己的处理器。
假设有一个非线程安全的类 ThreadUnsafeClass 在 ChannelHandler 的 channelRead 中调用,请问调用是否会有问题。 先说结论:没有问题。
如果ChannelHandler是非共享的,则它就是线程安全的, 原因为:当链路完成初始化时会创建ChannelPipeline,每个Channel对应一个ChannelPipeline实例,业务的ChannelHandler会被实例化并加入ChannelPipeline执行。由于 某个Channel只能被特定的NioEventLoop线程执行,因此ChannelHandler不会被并发调用,不用考虑线程安全问题。
这里说的线程安全问题,指的是同一个channel流水线中的所有ChannelHandler,会严格按照顺序由NioEventLoop组中某一个线程去执行。
业务可能会多线程调用ChannelHandlerContext或者Channel的write方法,这会不会导致多个业务线程并发调用ChannelHandler 呢?通过源码分析发现,不会产生并发安全问题。在执行write操作时,判断是否是下一个要执行write操作的AbstractChannelHandlerContext的EventExecutor线程,如果不是则将write操作封装成AbstractWriteTask放入线程任务队列异步执行,原调用线程返回。如果业务的ChannelHandler没有指定(通常不需要指定)EventExecutor线程,则使用的就是消息读写对应的 NioEventLoop线程。由此看见,即便多个业务线程并发调用某个Channel,也不会产生多个线程并发访问业务ChannelHandler的问题。源码如下(AbstractChannelHandlerContext类):
【共享ChannelHandler的线程安全问题】
如果ChannelHandler不是共享的,重复向ChannelPipeline添加时就会抛出ChannelPipelineException异常,添加失败。所以非共享的同一个ChannelHandler实例不能被重复加入多个ChannelPipeline或者被多次加入某一个ChannelPipeline。 通过 @ S harable 注解可以让不同的channel添加同一个ChannelHandler实例到自己的流水线中。
首先,netty的处理模型是存在一组IO线程,去处理IO事件,如read,connect,write等等,对于服务端接收到的每个channel,都会将该channel映射到一条IO线程。
当一个channel被建立之后,需要将其初始化,包含给他创建pipleline并填充channelhandler;给channel附以channelOptions和channelAttrs等,其中填充channelhandler可能会导致问题
如某个channelHandler是共享的,却被添加到多个channel上,而每个channel对应一个线程,必然会导致线程并发问题。
当ChannelHandler被添加到多个ChannelPipeline,就会面临多线程并发访问问题,需要ChannelHandler保证自身的线程安全,例如通过原子类、读写锁等方式对数据做并发保护。如果加锁,可能会阻塞NioEventLoop线程,所以Sharable注解的ChannelHandler要慎用。
【ChannelHandler并发总结】
用户自定义的ChannelHandler有两种场景需要考虑并发安全。
(1)通过Sharable注解,多个ChannelPipeline 共享的ChannelHandler, 它将被多个NioEventLoop线程( 通常用户创建的NioEventLoopGroup线程数>1)并发访问,如图7-5所示。在这种场景下,用户需要 保证ChannelHandler共享的合理性,同时需要自己保证它的 并发安全性 ,尽量通过原子类等方式降低锁的开销 ,防止阻塞NioEventLoop线程。 这里的并发安全在于,假如该共享ChannelHandler有某个标值类成员变量,不用的Channel可能会同时修改该变量,则会出现线程安全问题 。
(2)ChannelHandler没有共享,但是在用户的ChannelPipeline中的一些ChannelHandler绑定了新的线程池,这样ChannelPipeline的ChannelHandler就会被异步执行,如图7-6所示。在多线程异步执行过程中,如果某ChannelHandler的成员变量共享给其他ChannelHandler,那么被多个线程并发访问和修改就存在并发安全问题,如图7-7所示。