Netty的核心代码十分复杂,NioEventLoop是一个重量级类,我们没办法全部看一遍,这里我会选出核心的流程,带着问题去看源码,便会事半功倍,废话不多说,咱们开始
老规矩,先看看NioEventLoop的继承关系
NioEventLoop的设计原理
Netty的NioEventLoop并不是一个纯粹的I/O线程,它除了负责I/O的读写之外,还负责处理以下两种任务
- 系统的task:也就是普通的task任务,通过调用NioEventLoo的execute(task)方法实现,Netty有许多系统Task,为了更好协调用户线程和IO线程
- 定时任务:从继承关系中也看出,NioEventLoop实现了ScheduledExecutorService接口,所以可以调用NioEventLoop的schedule方法执行定时任务
- 明白一点:NioEventLoop=Selector+thread+taskQueue
- 底层使用单线程线程池执行任务和IO事件,熟悉线程池的应该明白,类似于线程池的SynchronousQueue阻塞队列
正式由于NioEventLoop具有多种职责,所以具体的实现源码十分复杂,接下来我们带着问题来看核心代码
1.selector是何时创建的,为什么会有两个selector成员变量
我们找到NioEventLoop的构造方法
NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler,
EventLoopTaskQueueFactory queueFactory) {
super(parent, executor, false, newTaskQueue(queueFactory), newTaskQueue(queueFactory),
rejectedExecutionHandler);
if (selectorProvider == null) {
throw new NullPointerException("selectorProvider");
}
if (strategy == null) {
throw new NullPointerException("selectStrategy");
}
provider = selectorProvider;
// 核心在此,我们点进去openSelector()方法一探究竟
final SelectorTuple selectorTuple = openSelector();
selector = selectorTuple.selector;
unwrappedSelector = selectorTuple.unwrappedSelector;
selectStrategy = strategy;
}
//openSelector()方法极其复杂,关于selector的初始化,核心在于此行代码
//可以看到openSelector()就是创建了一个selector对象
unwrappedSelector = provider.openSelector();
//对比Nio中的Selector.open()方法可以看到,Netty使用的也是相同的方法创建selector对象
SelectorProvider.provider().openSelector();
所以selector是在创建NioEventLoop对象调用构造方法时创建的
还有一个遗留问题,为什么有两个selector对象
在NioEventLoop中我们可以看到两个selector对象
- unwrappedSelector是Nio底层提供的,selector是Netty为了提升性能对原始对象的包装
- 我们通过selector.selectedKeys()来获取要监听的事件,Nio提供的是以set为容器的集合,但set涉及到散列表的遍历,为了追求效率,Netty包装了selector,用数组来作为容器,通过下标即可直接访问到对应的keys
- 但一些方法还是需要用到原始的selector提供的方法,所以索性就把两个都留下来,当用到selectedKeys相关时就用Netty包装的selector类
2.NioEventLoop的线程何时启动
前文我们说过NioEventLoop=Selector+thread+taskQueue
,那么thread是在何时启动的呢?
先说结论
thread是在第一次调用execute()方法时初始化创建,接下来我们进源码看看
//此方法继承于SingleThreadEventExecutor,所以这里的execute执行器只会支配一个thread工作线程,一个工作线程不断处理taskQueue任务和IO事件
@Override
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
boolean inEventLoop = inEventLoop();//@1
// 接下来会把task加入任务队列,此处和thread的初始化无关,咱不讨论