为什么80%的码农都做不了架构师?>>>
开门见山
之前的文章说了想要了解Netty服务端的秘密,就得先挖掘NioServerSocketChannelFactory到底做了些什么事情。ServerBootstrap绑定过程使用ChannelFactory创建Channel来完成绑定的一部分。
话不多说,下面我来看下NioServerSocketChannelFactory的创建过程。
NioServerSocketChannelFactory构造过程:
- 初始化两个Pool,一个BossPool,一个WorkerPool,默认使用NioServerBossPool和NioWorkerPool;
- 初始化NioServerSocketPipelineSink
看完BossPool和WorkerPool之后,你就会发现一旦NioServerSocketChannelFactory被构造之后,就启动了BossPool和WorkerPool里面的Boss/Worker线程了。
一个简单的new操作,就启动了Boss和Worker。
BossPool是什么
BossPool是维护Boss的一个容器,负责维护每个Boss对象的生命周期,包括创建、关闭。通过nextBoss()方法返回池子里的一个Boss对象。
AbstractNioBossPool是抽象父类,内部使用一个数组来充当容器,有两个子类,NioServerBossPool、NioClientBossPool。
NioServerBossPool构造过程:
- 传入Executor线程池,bossCount(作为BoosPool的容器大小,默认是1个),设置ThreadNameDeterminer(线程命名接口)
- 调用父类AbstractNioBossPool构造函数,设置相应的属性,创建Boss数组
- init()方法初始化
- 循环boss数组,依次调用newBoss()方法创建Boss对象
- waitForBossThreads ,等待每个boss线程运行,等待分析todo
- 回到NioServerBossPool,实现newBoss()方法,调用NioServerBoss构造函数创建对象
NioServerBoss继承AbstractNioSelector,是一个Boss接口。
NioServerBoss构造过程:
- 传入Executor线程池,ThreadNameDeterminer(线程命名接口)
- 调用父类AbstractNioSelector的构造器,设置Executor属性,这个Executor由NioServerBossPool传入
- openSelector
- open一个原生的NIO Selector
- 调用newThreadRenamingRunnable() 创建线程,丢入Executor中执行
- 回到NioServerBoss,实现newThreadRenamingRunnable()方法,就是使用本身创建ThreadRenamingRunnable
总结
调用了NioServerBossPool构造函数后,后续都发生了什么事情:创建bossCount个数量的Boss对象,每个Boss对象都打开一个Selector,并将自身丢入Executor线程池中执行,(这个Executor是由BossPool构造的时候传入的,因此每个Boss对象都共用同一个Executor)。在说的简单点,new NioServerBossPool()就是执行Boss线程。
new NioServerSocketChannelFactory() --------> new NioServerBoosPool() ---------> Boss线程运行
WorkerPool是什么
知道了BossPool,让我们看看WorkerPool,先不看源码猜测下应该和BossPool逻辑都是一样的。
WorkerPool是维护Worker的一个容器,负责维护每个Worker对象的生命周期,包括创建、关闭。通过nextWorker()方法返回池子里的一个Worker对象。
AbstractNioWorkerPool是抽象父类,内部使用一个数组来充当容器,有两个子类,NioWorkerPool、NioDatagramWorkerPool(UDP协议)。
和Boss一样Worker接口也继承Runnable接口,Worker有两类实现,分别是NIO和OIO:
- AbstractNioWorker,两个子类NioWorker、NioDatagramWorker
- AbstractOioWorker,两个子类OioWorker、OioDatagramWorker
NioWorkerPool构造过程:
- 传入Executor线程池,workerCount(作为WorkerPool的容器大小,默认是CPU * 2),设置ThreadNameDeterminer(线程命名接口)
- 调用父类AbstractNioWorkerPool构造函数,设置相应的属性,创建Worker数组
- init()方法初始化
- 循环worker数组,依次调用newWorker()方法创建Worker对象
- waitForWorkerThreads ,等待每个worker线程运行,等待分析todo
- 回到NioWorkerPool,实现newWorker()方法,调用NioWorker构造函数创建对象
NioWorker继承AbstractNioWorker,是一个Worker接口。
NioWorker构造过程:
- 传入Executor线程池,ThreadNameDeterminer(线程命名接口)
- 调用父类AbstractNioWorker的构造器,AbstractNioWorker继承AbstractNioSelector
- 调用父类AbstractNioSelector的构造器,设置Executor属性,这个Executor由NioWorkerPool传入
- openSelector
- open一个原生的NIO Selector
- 调用newThreadRenamingRunnable() 创建线程,丢入Executor中执行
- 回到NioWorker,实现newThreadRenamingRunnable()方法,就是使用本身创建ThreadRenamingRunnable
总结
调用了NioWorkerPool构造函数后,后续都发生了什么事情:创建workerCount个数量的Worker对象,每个Worker对象都打开一个Selector,并将自身丢入Executor线程池中执行,(这个Executor是由WorkerPool构造的时候传入的,因此每个Worker对象都共用同一个Executor)。在说的简单点new NioWorkerPool()就是执行Worker线程。
new NioServerSocketChannelFactory() --------> new NioWorkerPool() ---------> Worker线程运行
Boos和Worker流程是一样的
上面的分析乍一看,BoosPool和WorkerPool构造的流程都是一样的,都是创建N个Boss/Worker,一旦创建就丢入线程池中运行。需要注意的是Worker同样也打开了Selector。那么Boss和Worker两者的区别是什么呢,那就是两者的run()方法的逻辑了。
生产NioServerSocketChannel来做什么
NioServerSocketChannel构造过程:创建NIO的ServerSocketChannel,触发一个OPEN的Upstream Event,结束,就这样简单。
那么怎么启动服务端呢,那就是调用NIOServerSocketChannel的bind()方法(不管是在ServerBootstrap的Binder类还是主动去调用)。让我们看下Channel的bind()方法做了什么,其实很简单Channel的bind()方法是触发一个BOUND的Downstream Event。
顺便一提Channel的一些列主动的方法,比如bind、connect等都是触发一个相同类型的Downstream Event,在Pipeline中流转完之后,最终交给ChannelSink去处理。
所有可以这么认为,Channel的一系列动词的方法,就直接去看对应的ChannelSink的处理逻辑。
NioServerSocketChannel对应的是NioServerSocketPipelineSink,NioServerSocketPipelineSink里处理的BOUND的Event交给了Boss对象的bind方法。那NioServerBoss的bind做了些什么,生成了一个RegisterTask放到boss的任务队列里等待线程执行。这个RegisterTask是要做什么,是使用NIO ServerSocketChannel来执行bind逻辑,然后注册到Selector中。
总结一下
Channel的bind --------> ChannelSink ------> NioServerBoss.bind ------> RegisterTask -------> NIO的绑定和注册逻辑
另外
ThreadRenamingRunnable
ThreadRenamingRunnable本身实现Runnable,它接收一个Runnable对象,在run方法中会将线程名重命名,执行Runnable对象的run方法,完了之后重置线程名。线程名重命名交给ThreadNameDeterminer接口。因此可以简单的认为ThreadRenamingRunnable等同于它的Runnable属性。
DeadLockProofWorker
start(final Executor parent, final Runnable runnable),使用parent去执行runnable,这个类是做什么的?为什么这么设计?