三个问题
1.默认情况下,Netty服务端起多少线程?何时启动?
答: 2*CPU核数个线程。在调用execute方法时,会判断当前是否在本线程,如果是在本线程,说明线程已经启动;如果是在web线程,调用execute方法,首先会调用startThread方法,这个方法首先会判断当前线程是否启动,如果没有启动就启动该线程。
2.Netty是如何解决jdk空轮询bug的?
Netty是通过计数的方式判断的。如果当前阻塞的一个select操作实际上并没有花那么长时间,那么这一次就可能触发了空轮询bug,默认情况下如果这个现象达到512次,然后就重建一个selector,把之前selector上面所有的key重新移交到新的selector,通过这种方式来巧妙的避免了空轮询bug。
3.Netty如何保证异步串行无锁化?
常见的场景就是在拿到客户端的Channel,而不需要对这个Channel进行同步就直接可以多线程并发读写,而另外一个就是Channel Header里面所有的操作都是线程安全的,不需要进行同步。Netty在所有的web线程去调用EventLoop或者Channel里方法的时候,通过inEventLoop这个方法来判断得出是web线程,这种情况下就把所有的操作封装成一个task,丢到MpscQueue里面,然后在NioEventLoop 执行逻辑的第三个过程,这些task会被依次执行。
NioEventLoop
(1)NioEventLoop创建
(2)NioEventLoop启动
(3)NioEventLoop执行逻辑
(1)NioEventLoop创建
new NioEventLoopGroup()[线程组,默认2*cpu]
new ThreadPerTaskExecutor()[线程创建器]
for(){newChid()}[构造NioEventLoop对象数组]
chooserFactory.newChooser()[线程选择器]
(a)ThreadPerTaskExecutor()
- 每次执行任务都会创建一个任务实体
- NioEventLoop线程命名规则nioEventLoop-1-xx
(b)newChid()
- 保存线程执行器ThreadPerTaskExecutor
- 创建一个MpscQueue
- 创建一个selector
(c)newChooser()
- isPowerOfTwo()[判断是否是2的幂,如2、4、8、16]
PowerOfTwoEventExecutorChooser[是2的幂,优化]
index++&(length-1)//也可以实现从0到最后一个,再从0到最后一个的循环操作
GenericEventExecutorChooser[普通]
abs(index++%length)//从0到最后一个,再从0到最后一个循环
- index++&(length-1)运行过程:
index 111010
&
executors.length-1 1111
result 1010
原因:java底层中 &操作比%操作更快
(2)NioEventLoop启动
- 服务端启动绑定本地端口
- 新连接接入通过chooser绑定一个NioEventLoop
bind()—>execute(task)[入口]
startThread()—>doStartThread()[创建线程]
ThreadPerTaskExecutor.execute()
thread=Thread.currentThread()
NioEventLoop.run()[启动]
(3)NioEventLoop执行逻辑
SingleThreadEventExecutor.this.run()
NioEventLoop.run()—>for(;;)
select()[检查是否有io事件]
processSelectedKeys()[处理从上一过程中轮询出来的io事件]
runAllTasks()[处理异步任务队列]
(a)检测IO事件
select()方法执行逻辑
- deadline以及任务穿插逻辑处理
- 阻塞式select
- 避免jdk空轮询的bug
(b)处理IO事件
processSelectedKeys()方法执行逻辑
- selected keySet优化
- processSelectedKeysOptimized()
(c)reactor线程任务的执行
runAllTasks()方法执行逻辑
- task的分类和添加
- 任务的聚合:将定时任务队列里的task聚合到普通的taskQueue里面
- 任务的执行