NioEventLoop源码解析
共 8366字,需浏览 17分钟
·
2021-07-04 00:21
有道无术,术尚可求也!有术无道,止于术!
源码分析
上一节课,我们就 new NioEventLoopGroup();
的初始化过程做了一个深度的解析,后来我们发现,NioEventLoopGroup在初始化过程中会构建一个执行器数组,数组内部存储的元素是NioEventLoop
类型的,但是NioEventLoop是什么呢?为什么说他是Netty的精髓呢?
我们直接进入到NioEventLoop看他的构造方法:
上一节课我们是在循环填充执行器数组的过程中创建的,具体参见上一节课的for循环中的 newChild方法,这里直接分析源码
NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler,
EventLoopTaskQueueFactory queueFactory) {
//保存外部线程任务newTaskQueue(queueFactory)
super(parent, executor, false, newTaskQueue(queueFactory), newTaskQueue(queueFactory),
rejectedExecutionHandler);
this.provider = ObjectUtil.checkNotNull(selectorProvider, "selectorProvider");
this.selectStrategy = ObjectUtil.checkNotNull(strategy, "selectStrategy");
final SelectorTuple selectorTuple = openSelector();
this.selector = selectorTuple.selector;
this.unwrappedSelector = selectorTuple.unwrappedSelector;
}
关于super我们一会再往后追
一、保存选择器生产者
this.provider = ObjectUtil.checkNotNull(selectorProvider, "selectorProvider");
是绑定了一个类似于生产者的东西,使我们再初始化NioEventLoopGroup的时候初始化的,使用该生产者,后续可以获取选择器或者Socket通道等!
二、保存选择器
this.selectStrategy = ObjectUtil.checkNotNull(strategy, "selectStrategy");
保存一个默认的选择策略到NioEventLoop对象里面
三、开启一个选择器
final SelectorTuple selectorTuple = openSelector();
开启一个选择器包装对象,内含一个选择器!Netty官方为了Netty性能的进一步优化,丧心病狂的对这个选择器也进行了优化,我们跟进一下openSelector方法,看看他是如何优化的,内部代码比较复杂,我们逐行分析:
1. 获取原始的选择器
unwrappedSelector = provider.openSelector();
使用原始的生产者对象,获取一个原始的选择器,后续使用!
2. 判断是否启动用选择器优化
//禁用优化选项 默认false
if (DISABLE_KEY_SET_OPTIMIZATION) {
//如果不优化那么就直接包装原始的选择器
return new SelectorTuple(unwrappedSelector);
}
DISABLE_KEY_SET_OPTIMIZATION默认为false, 当禁用优化的时候,会将selector选择器直接进行包装返回! 默认会进行优化,所以一般不会进这个逻辑分支!
3. 获取一个选择器的类的对象
//如果需要优化
//反射获取对应的类的对象 SelectorImpl
Object maybeSelectorImplClass = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
return Class.forName(
"sun.nio.ch.SelectorImpl",
false,
PlatformDependent.getSystemClassLoader());
} catch (Throwable cause) {
return cause;
}
}
});
这个代码是返回一个 SelectorImpl的Class对象,这里是返回SelectorImpl的Class对象!我们由上述代码可以看出来,如果获取失败,会返回一个异常,异常的话肯定不行,所以就要对可能会发生的异常做出操作:
//如果没有获取成功
if (!(maybeSelectorImplClass instanceof Class) ||
// 确保当前的选择器实现是我们可以检测到的。 判断是 unwrappedSelector的子类或者同类
!((Class<?>) maybeSelectorImplClass).isAssignableFrom(unwrappedSelector.getClass())) {
//发生异常
if (maybeSelectorImplClass instanceof Throwable) {
Throwable t = (Throwable) maybeSelectorImplClass;
logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, t);
}
//还是包装为未优化的选择器
return new SelectorTuple(unwrappedSelector);
}
如果发生了异常,或者获取的和原始选择器不是一个对象,就还使用原始选择器包装返回!
4. 创建一个优化后的selectKeys
final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
使用过NIO的都应该知道,使用选择器是能够获取一个事件的Set集合的,这里Netty官方自己实现了一个Set集合,内部使用数组来进行优化!因为Hashset集合是使用HashMap方法来实现的,(this ->Object) 再添加元素的时候如果发生了hash碰撞的话会遍历hash槽上的链表 算法复杂度为O(n),但是数组不一样 数组是O(1) 所以Netty官方使用数组来优化选择器事件集合 默认是1024 满了之后2倍扩容!
大家可以简单的把它看做一个Set集合,只不过他是使用数组的形式来实现的!内部重写了add、size、iterator的方法,其余方法全部废弃!
这个是Netty对选择器优化的一个重要对象,使得再追加事件的时候,算法复杂度由O(N)直接变为了O(1)!
5. 开始进行反射替换selectedKeys
//开始进行反射替换
Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
//获取选择器事件中的 事件对象 selectedKeys的属性对象
Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
//获取公开选择的密钥
Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");
//java9且存在 Unsafe的情况下 直接替换内存空间的数据
if (PlatformDependent.javaVersion() >= 9 && PlatformDependent.hasUnsafe()) {
// 让我们尝试使用sun.misc.Unsafe替换SelectionKeySet。
// 这使我们也可以在Java9 +中执行此操作,而无需任何额外的标志。
long selectedKeysFieldOffset = PlatformDependent.objectFieldOffset(selectedKeysField);
long publicSelectedKeysFieldOffset =
PlatformDependent.objectFieldOffset(publicSelectedKeysField);
if (selectedKeysFieldOffset != -1 && publicSelectedKeysFieldOffset != -1) {
PlatformDependent.putObject(
unwrappedSelector, selectedKeysFieldOffset, selectedKeySet);
PlatformDependent.putObject(
unwrappedSelector, publicSelectedKeysFieldOffset, selectedKeySet);
return null;
}
// 如果没法直接替换内存空间的数据 就想办法用反射
}
//java8或者java9+上述未操作完成的 使用反射来替换
Throwable cause = ReflectionUtil.trySetAccessible(selectedKeysField, true);
if (cause != null) {
return cause;
}
cause = ReflectionUtil.trySetAccessible(publicSelectedKeysField, true);
if (cause != null) {
return cause;
}
//开始进行替换 将我们创建的优化后的事件数组来反射的替换进选择器中
selectedKeysField.set(unwrappedSelector, selectedKeySet);
publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);
return null;
} catch (NoSuchFieldException e) {
return e;
} catch (IllegalAccessException e) {
return e;
}
}
});
代码虽然多,但是,逻辑比较简单!
首先获取 SelectorImpl
类对象的selectedKeys
属性和publicSelectedKeys
属性!判断使用的JDK版本是不是9以上,如果使用的9的话,直接操作JAVA的Unsafe对象操作系统的内存空间!有关Unsafe的介绍,再零拷贝章节介绍的很详细,可以复习零拷贝章节! 我们这里使用的JDK8 如果使用的是JDK8,使用反射,将我们创建出来的SelectedKeys的优化对象 SelectedSelectionKeySet
反射的替换进unwrappedSelector
这个原始的选择器!
6. 包装选择器
return new SelectorTuple(unwrappedSelector, new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet));
首先把unwrappedSelector选择器包装为 SelectedSelectionKeySetSelector包装类!
再把unwrappedSelector和SelectedSelectionKeySetSelector对应起来,包装Wie元组返回!
四、保存优化后的选择器和原始选择器
this.selector = selectorTuple.selector;
this.unwrappedSelector = selectorTuple.unwrappedSelector;
五、调用父类,创建队列
super(parent, executor, false, newTaskQueue(queueFactory),
newTaskQueue(queueFactory),rejectedExecutionHandler);
首先,他会通过newTaskQueue
构建两个队列 ,这两个队列是什么类型的呢?
上一节课我们分析过,queueFactory == null,所以会走如图分支代码,DEFAULT_MAX_PENDING_TASKS
如果没有指定的话,默认是Integer.MAX,最小为16!我们进入到 该分支代码看一下,他创建的是一个什么队列:
public static <T> Queue<T> newMpscQueue() {
return Mpsc.newMpscQueue();
}
可以看出他创建的是一个Mpsc队列,他是一个多生产者,单消费者队列,是由jctools
框架提供的,后续如果可以,我会具体对该队列进行一个讲解,我们到这里就知道,再创建NIOEventLoop的时候,向父类内部传递了两个Mpsc队列,我们继续回到主线:
进入到super(xxx)的源码中:
protected SingleThreadEventLoop(EventLoopGroup parent, Executor executor, boolean addTaskWakesUp, Queue<Runnable> taskQueue, Queue<Runnable> tailTaskQueue,
RejectedExecutionHandler rejectedExecutionHandler) {
super(parent, executor, addTaskWakesUp, taskQueue, rejectedExecutionHandler);
//保存一个 tailTasks 尾部队列
tailTasks = ObjectUtil.checkNotNull(tailTaskQueue, "tailTaskQueue");
}
这里会保存一个队列,尾部队列,这个尾部队列,官方的意思是想对Netty 的运行状态做一些统计数据,例如任务循环的耗时、占用物理内存的大小等等,但是实际上应用tailTasks的场景极少,这里不做太多讲解!
我们继续跟进到super方法源码里面:
//parent 线程执行器 false mpsc队列 拒绝策略
protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor,
boolean addTaskWakesUp, Queue<Runnable> taskQueue,
RejectedExecutionHandler rejectedHandler) {
super(parent);
this.addTaskWakesUp = addTaskWakesUp;
this.maxPendingTasks = DEFAULT_MAX_PENDING_EXECUTOR_TASKS;
//保存线程执行器
this.executor = ThreadExecutorMap.apply(executor, this);
//创建一个队列 Mpscq,外部线程执行的时候使用这个队列(不是在EventLoop的线程内执行的时候) newTaskQueue(queueFactory)
this.taskQueue = ObjectUtil.checkNotNull(taskQueue, "taskQueue");
//保存拒绝策略
this.rejectedExecutionHandler = ObjectUtil.checkNotNull(rejectedHandler, "rejectedHandler");
}
这里是进一步保存,将该NioEventLoop对应的线程执行器 、MpscQuerey任务队列、对应的拒绝策略保存起来! 大家再后续看到使用对应变量的代码千万不要觉得陌生哦!
总结
创建和保存了两个多生产者单消费者队列 tailTasks
和taskQueue
保存一个线程执行器 executor
保存一个拒绝策略,该拒绝策略主要用于队列满了之后如何处理! 保存一个选择器生产者! 创建一个优化后的选择器,并进行保存! 将原始选择器和优化后的选择器进行保存!
才疏学浅,如果文章中理解有误,欢迎大佬们私聊指正!欢迎关注作者的公众号,一起进步,一起学习!
❤️「转发」和「在看」,是对我最大的支持❤️