Netty解读源码之---EventLoopGroup,EventExecutorGroup,EventLoop,EventExecutor详解

     不知大家有没有这样的经历,准备兴致勃勃的看netty源码时发现被第一行的NioEventLoopGroup组件就搞的晕头转向。我觉得想看懂netty源码必须得先了解清楚它的事件轮询机制以及ChannelPipeline的责任链机制。从今天开始我准备写一系列的netty源码解读文章,主要风格还是跟之前的《从根儿上学习spring》一样先了解基础组件,再从启动流程中窥见netty源码的核心流程。

这篇我们讲讲netty事件轮询的几个接口:EventLoopGroup,EventExecutorGroup,EventLoop,EventExecutor的作用以及其各之间的关系,为后面讲解事件轮询做下铺垫。

    先说说EventExecutorGroup吧,因为其它几个几乎都实现了EventExecutorGroup。从EventExecutorGroup名字上我们大可以可以猜测它是一组EventExecutor的集合,肯定内部维护了多个EventExecutor。其实现了ScheduledExecutorService接口说明其主要是用来处理runable或者callable任务的,而实现Iterable<EventExecutor>接口说明其可以遍历获取其内部维护的EventExecutor。通过接口定义我们也只能知道这么多信息,其实它也就干这些事,大家先做个简单了解即可。

图1

     再来看看EventExecutor,从名字上看它一个事件执行器,它实现了EventExecutorGroup接口,所以它有EventExecutorGroup的所有能力也就是可以用来执行任务。从该类的注释说它是一个特殊的EventExecutorGroup,提供了可以校验一个线程是否被执行在一个Event loop中,即事件循环里,也就是我们后面要说的EventLoop。其实这里说的Event Loop就是由EventExecutor创建的线程任务,而EventExecutor就是额外提供了判断当前执行线程是否跑在由它创建的线程任务中而已。判断逻辑也很简单,就是每个EventExecutor维护一个Thread对象,开启线程后把新开启的线程赋值给Thread成员变量,后面使用Thread.currentThread() == Thread成员变量 ,相等就表示在Event Loop事件中。

不过从接口设计上,感觉多少有点让人疑惑,从类的命名上明显是EventExecutorGroup管理维护EventExecutor的,它们之间包含和被包含关系。但是注释又说EventExecutor是一种特殊的EventExecutorGroup似乎两者又是平级关系。不过从能力上确实是平级关系,毕竟EventExecutorGroup有的功能EventExecutor都有,个人感觉这一点设计的不是很好(有大佬有不同想法可以评论区交流一下)

总的来说EventExecutorGroup和EventExecutor就可以理解为一个可以执行runable任务的执行器,内部对jdk的父接口ScheduledExecutorService的方法做了些重写,主要就是把返回类型包装成netty内部定义的实现类,这个后面再说吧,也没什么就是netty不想用jdk提供的Future。

图2

       接着说说EventLoopGroup,从名字上看可以猜到它是EventLoop的集合,内部可以有多个EventLoop。从该接口的定义看让人有点疑惑,又继承了EventExecutorGroup只是多加了几个register方法,从该接口定义看EventLoopGroup和EventExecutorGroup几乎没啥区别,也可以用来执行任务只是多了几个register方法而已,而EventLoop的定义更简单就不列出来了,只是只是简单的继承了EventLoopGroup和OrderedEventExecutor。

之所以又搞出个EventLoopGroup主要是为了把Channel对象注册到EventLoopGroup里,因为后面的Channel所有IO事件都会在具体的EventLoop的线程上执行以保证线程安全和有序性。

图3

说实话但从接口定义上看不太清楚这四个接口的关系以及事件循环Event loop的含义,如果从其子类看的话说实话感觉还是有点乱毕竟它们各种互相继承/实现。我们还是从一个netty服务端的初始化流程来看吧

图4

如图4所示,一般我们会实例化两个NioEventLoopGroup作为启动类ServerBootstrap的group参数。那么我们看看NioEventLoopGroup 实例化过程。

图5

如上图5所示,NioEventLoopGroup的构造器一通调用后最终会走到其父类MultithreadEventExecutorGroup的构造器,该构造主要就是调用newChild(executor, args)方法对其属性EventExecutor[] children的每个元素进行赋值。从这里也能看出EventExecutorGroup确实内部会维护多个EventExecutor。

不知大家看到这个newChild(executor, args)方法会不会有个疑问,需要创建的对象本身就是EventExecutor类型的,为什么方法参数还要传一个executor呢?大家别慌78行这个executor一般会使用默认的ThreadPerTaskExecutor对象,主要是EventLoop真正用来创建线程执行任务的。你可以想象下前面我们说过所有的EventLoop都继承了 EventExecutorGroup,而EventExecutorGroup又继承了ScheduledExecutorService,所以最终它肯定要通过submit或者execute来执行任务,没错就在eventLoop的execute方法里会调用这里的ThreadPerTaskExecutor对象真正创建线程。没看懂?没事先别管了,接着往下看吧老铁后面会懂的。

newChild该方法在NioEventLoopGroup类里做了实现,io.netty.channel.nio.NioEventLoopGroup#newChild,如下图6所示。该方法创建了NioEventLoop对象。也就是说对于上面图5创建的数组EventExecutor[] children中的元素是NioEventLoop对象。也没毛病比较NioEventLoop实现了EventExecutor接口。

图6

看到这里目前来说应该还算思路清晰还没晕吧?当前大家心里要有这么一个意识,EventExecutorGroup就是一个执行任务的执行器有点类似与线程池一样,EventLoopGroup只是一种特殊的EventExecutorGroup多了点功能而已,复杂的是后面它们和其它组件的组合使用,比如channel组件,ChannelFuture等这些后面再看。我们接着往下看io.netty.channel.nio.NioEventLoop的实例逻辑。

追踪NioEventLoop的构造器最终会发现参数executor赋值给了NioEventLoop的抽象父类SingleThreadEventExecutor的private final Executor executor属性。对象创建流程差不多结束了,但真正逻辑肯定是执行任务啊,那么我们得找找执行任务的方法看看它们的逻辑做了啥事才能更好的理解这些接口的功能。

作为EventExecutorGroup的实现类SingleThreadEventExecutor或其父类必将实现submit和execute方法,这个没啥好说的因为其顶级接口EventExecutorGroup实现了jdk的java.util.concurrent.ScheduledExecutorService接口。那我们就找找哪里实现了submit方法吧,在SingleThreadEventExecutor的父类AbstractEventExecutor找到了其实现了submit方法

图7

可以看到在抽象类AbstractEventExecutor中调用了父类的submit方法,其父类是jdk中的抽象类AbstractExecutorService。也就是说submit方法在netty中重写的最主要目的就是为了将返回值从jdk自带的Future修改为netty自定义的Future接口。而执行任务主要是通过netty重写的execute方法(submit内部会调用其子类的execute方法)。okk那我们只能再去找哪里调用了execute方法。

图8

真巧啊,在SingleThreadEventExecutor里重写了execute方法,跟着调用链最终会调用图9下面这个execute方法

图9

这个方法是任务执行的核心方法,第一行调用inEventLoop()方法,我们先不看这个方法的含义,先只看这个方法代码执行的逻辑。该方法最终会调用当前SingleThreadEventExecutor的inEventLoop(Thread thread)参数线程对象是通过Thread.currentThread()获取的,在inEventLoop(Thread thread)方法内会拿参数thread对象和SingleThreadEventExecutor类的private volatile Thread thread;属性做==比较,两个对象相同则返回true,这个上面我已经说过。

接着执行addTask方法,这个方法很简单就是把task对象添加到队列里,大家自己翻代码看下吧。

如果if(!inEventLoop)为true,则调用startThread()方法。

图10

在startThread方法中通过一个state成员变量和cas控制是否已经启动过线程,未启动过则再调用doStartThread方法。

图11

在doStartThread方法中通过构造器参数传进来executor对象调用其execute方法执行任务,在图11的1062行这里我们也可以看到给tread对象进行赋值的操作。

到这里我们简单梳理一下。也就是SingleThreadEventExecutor类执行任务的逻辑是把任务放到队列里,严格控制只用一个线程去执行。这个任务线程是通过构造器传进来的executor执行execute方法创建的,如果大家在一开始有注意到的话,在图5的80行会默认创建一个ThreadPerTaskExecutor对象传过来,ThreadPerTaskExecutor每次执行execute时都会创建一个新线程来执行任务。

不知道大家有没有一个疑问SingleThreadEventExecutor本身就是一个executor为什么执行任务时却还需要另一个Executor呢?我理解主要还是因为为了不阻塞主任务,比如netty在nio模型中监听到可读事件后,将读任务提交到额外的独立线程(池)中执行,为了不阻塞nio的主线程。个人理解有理解不到位的地方还望大佬们指点

ok这篇为了防止内容过多就先讲到这,希望大家多讨论将内容嚼碎更利于彼此吸收。下一篇我们讲讲事件轮询到底是个啥。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值