Netty核心之线程模型

Posted 漫谈Java架构

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Netty核心之线程模型相关的知识,希望对你有一定的参考价值。

前言:

前面章节我们对Netty的整体结构和使用流程进行了剖析,使用过程中我们首先创建了两个线程组EventLoopGroup,一个负责连接分派,一个负责IO读写,那么这两个线程组工作原理是怎么样的呢?由于NioEventLoopGroup应用较为广泛,我们从这个线程组开刀!

1:Reactor模型:

Reactor模型分为三种,根据并发的用户量性能由低到高

第一种是单线程接收多客户端连接请求并亲自处理IO读写

第二种单线程接收多客户端连接请求请求,转发给其他线程池进行IO读写

第三种多线程接收多客户端连接请求,转发给其他线程池进行IO读写

Netty可以完全吸纳该模型,server端可以通过设置bossgroup和workergroup来选择使用第二种和第三种模型。

2:NioEventLoop线程模型:

我们用到的线程组EventLoopGroup workerGroup = new NioEventLoopGroup()实际上是有每个NioEventLoop线程模型组成的。

类似于web天生的多线程,只要每个业务逻辑的Handler(车间)是无状态的,那么所有的NioEventLoop(工人)就可以并发执行,而不需要加锁。

A:NioEventLoop原理分析:

NioEventLoop类图

由该类图可以看出NioEventLoop线程模型顶层接口实现了ScheduledExecutorService,这是Java concurrent包里面的接口,该接口可以执行定时任务,所以毫无疑问,NioEventLoop模型也能执行任务并且能执行定时任务!

打开源码,我们发现NioEventLoop内部依赖了一个Selector,这个Selector是Java NIO中的Selector,所以可见Netty本质上并不是AIO而是NIO。

那么,NioEventLoop到底能干哪些事呢?

a:打开了Selector,run方法中,根据是否有要执行的任务来选择调用selectNow方法和select方法(该方法没有会轮询),值得深思的是一个线程模型打开一个Selector。

b:注册通道,注册通道方法register,通过该方法绑定通道和线程。

c:执行任务,run方法最终调用的processSelectedKey方法是真正执行任务的代码,该方法通过Java NIO中的SelectionKey和channel真正执行执行通道的IO读写操作。

以上过程是不是有种似曾相识的感觉?没错,就是Java NIO的操作流程的封装!

3:NioEventLoopGroup原理分析:

我们前面说了NioEventLoop能干什么,但是并没有说怎么干的,谁指使他干的?我们下面回答这个问题。

首先,我们新建NioEventLoopGroup线程组,追踪其源码调用链

Netty核心之线程模型

1

Netty核心之线程模型

2

Netty核心之线程模型

3

Netty核心之线程模型

4

Netty核心之线程模型

5

Netty核心之线程模型

6

好,追踪到这里我们可以发现,在不填写参数的情况下创建的线程数是CPU个数的2倍!继续往下追踪.....

Netty核心之线程模型

7

Netty核心之线程模型

7.1

调用链到这里只有nThreads是有值的,executor为空,所以新建了一个ThreadPerTaskExecutor,这个类本质上是一个Java的Executor线程执行器,可以执行任务,继续。。。。

Netty核心之线程模型

7.2

还是7环节中的构造方法,我们看到,这个MultiThreadEventLoopGroup内置了一个线程执行器数组,数组长度与线程个数相同,下面调用了newChild给每个执行器数组元素赋值,点击进入newChild方法,由于我们调用的是NioEventLoopGroup,所以进入NioEventLoop的实现。。。。

Netty核心之线程模型

7.3

Netty核心之线程模型

7.3.1

Netty核心之线程模型

7.3.2

看见没,这个new NioEventLoop(this,executor,(SelectorProvider)args[0]);方法,这就是最终NioEventLoopGroup线程组的组成元素,构造NioEventLoop过程中给它传入了我们的ThreadPerTaskExecutor执行器。最后这个调用链还调用了openSelector()启动Selector!!!

总结:压缩以上过程,NioEventLoopGroup通过内置EventExecutor数组而实现线程组,而EventExecutor数组内部元素是NioEventLoop,所以可以说NioEventLoopGroup在构造过程中创建了2*CPU个NioEventLoop待用!!!!

4:启动:

前面我们剖析了NioEventLoopGroup是怎么由NioEventLoop构成的,下面我们分析下NioEventLoop是怎么工作的!

Netty核心之线程模型

1

我们直接看图1,bootstrap.bind方法,点击进入调用链

Netty核心之线程模型

2

绑定IP和端口。。。

Netty核心之线程模型

3

Netty核心之线程模型

4

调用至此,首先initAndRegister方法初始化了一条通道,并把ChannelFuture预期也绑定到了通道上。...

Netty核心之线程模型

4.1

可以看到initAndRegister创建了一条通道并对通道进行了初始化

Netty核心之线程模型

4.1.1

Netty核心之线程模型

4.1.1.1

这是ServerBootstrap中的方法,group()方法返回的实际上是bossGroup,next()方法则从bossGroup中返回一个NioEventLoop,一直追踪newChannel方法会发现,bossGroup中的NioEventLoop最终执行了register0方法,register0方法内部又注册了selector的感兴趣事件,for循环执行,并且内部新建了一个PipeLine流水线,所以bossGroup中的线程一直起到了监听和初始化的作用!!!

注意newChannel里面的childGroup,这个childGroup实际上是bootstrap.group(bossGroup,workerGroup)中的workerGroup!!!所以真正绑定到通道上的是workerGroup中的NioEventLoopGroup!!!

我们继续追踪doBind0方法.....

Netty核心之线程模型

5

Netty核心之线程模型

6

Netty核心之线程模型

7

Netty核心之线程模型

8

8.1

doBind0中我们发现,刚刚初始化并且绑定了NioEventLoop的方法调用了eventLoop()方法得到了一个NioEventLoop,然后调用NioEventLoop的execute方法执行一个任务,execute方法是NioEventLoop的父类SingleThreadEventExecutor的方法,该方法内部调用了startThread方法、doStartThread方法,最终到executor.execute方法,而这里的executor就是我们在new NioEventLoopGroup的内部创建的那个ThreadPerTaskExecutor, 该方法中调用了SingleThreadEventExecutor.this.run(),而这个run方法正是我们的NioEventLoop的run方法!!!

我们再次审视下ThreadPerTaskExecutor这个类:

9

刚刚的execute方法就是这里的execute,这里先利用线程工厂新建了一个线程,然后调用了线程的start()方法,真正的启动了NioEventLoop这个线程的run方法!!!

OK,以上这个过程解释了NioEventLoop线程模型怎么干的问题!

总结:追踪源码是个很令人头疼的问题,由于现在的编程都是面向接口编程,所以我们观察的时候要十分注意不要看错了方法,有的方法是父类的方法,有的方法是子类本身的方法!!!


以上是关于Netty核心之线程模型的主要内容,如果未能解决你的问题,请参考以下文章

死磕Netty源码之Reactor线程模型

Netty框架之线程模型与基础用法

Netty框架之线程模型与基础用法

Netty之Reactor线程模型概述

Netty线程模型

Netty框架之核心组件