NioEventLoop源码解析

开发 前端
NioEventLoopGroup在初始化过程中会构建一个执行器数组,数组内部存储的元素是NioEventLoop类型的,但是NioEventLoop是什么呢?为什么说他是Netty的精髓呢?

[[409020]]

本文转载自微信公众号「源码学徒」,作者皇甫嗷嗷叫 。转载本文请联系源码学徒公众号。

 源码分析

上一节课,我们就 new NioEventLoopGroup();的初始化过程做了一个深度的解析,后来我们发现,NioEventLoopGroup在初始化过程中会构建一个执行器数组,数组内部存储的元素是NioEventLoop类型的,但是NioEventLoop是什么呢?为什么说他是Netty的精髓呢?

我们直接进入到NioEventLoop看他的构造方法:

上一节课我们是在循环填充执行器数组的过程中创建的,具体参见上一节课的for循环中的 newChild方法,这里直接分析源码

  1. NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider, 
  2.              SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler, 
  3.              EventLoopTaskQueueFactory queueFactory) { 
  4.     //保存外部线程任务newTaskQueue(queueFactory) 
  5.     super(parent, executor, false, newTaskQueue(queueFactory), newTaskQueue(queueFactory), 
  6.           rejectedExecutionHandler); 
  7.     this.provider = ObjectUtil.checkNotNull(selectorProvider, "selectorProvider"); 
  8.     this.selectStrategy = ObjectUtil.checkNotNull(strategy, "selectStrategy"); 
  9.     final SelectorTuple selectorTuple = openSelector(); 
  10.     this.selector = selectorTuple.selector; 
  11.     this.unwrappedSelector = selectorTuple.unwrappedSelector; 

关于super我们一会再往后追

一、保存选择器生产者

  1. this.provider = ObjectUtil.checkNotNull(selectorProvider, "selectorProvider"); 

是绑定了一个类似于生产者的东西,使我们再初始化NioEventLoopGroup的时候初始化的,使用该生产者,后续可以获取选择器或者Socket通道等!

二、保存选择器

  1. this.selectStrategy = ObjectUtil.checkNotNull(strategy, "selectStrategy"); 

保存一个默认的选择策略到NioEventLoop对象里面

三、开启一个选择器

  1. final SelectorTuple selectorTuple = openSelector(); 

开启一个选择器包装对象,内含一个选择器!Netty官方为了Netty性能的进一步优化,丧心病狂的对这个选择器也进行了优化,我们跟进一下openSelector方法,看看他是如何优化的,内部代码比较复杂,我们逐行分析:

1. 获取原始的选择器

  1. unwrappedSelector = provider.openSelector(); 

使用原始的生产者对象,获取一个原始的选择器,后续使用!

2. 判断是否启动用选择器优化

  1. //禁用优化选项  默认false 
  2. if (DISABLE_KEY_SET_OPTIMIZATION) { 
  3.     //如果不优化那么就直接包装原始的选择器 
  4.     return new SelectorTuple(unwrappedSelector); 

DISABLE_KEY_SET_OPTIMIZATION默认为false, 当禁用优化的时候,会将selector选择器直接进行包装返回! 默认会进行优化,所以一般不会进这个逻辑分支!

3. 获取一个选择器的类的对象

  1. //如果需要优化  
  2. //反射获取对应的类的对象 SelectorImpl 
  3. Object maybeSelectorImplClass = AccessController.doPrivileged(new PrivilegedAction<Object>() { 
  4.     @Override 
  5.     public Object run() { 
  6.         try { 
  7.             return Class.forName( 
  8.                 "sun.nio.ch.SelectorImpl"
  9.                 false
  10.                 PlatformDependent.getSystemClassLoader()); 
  11.         } catch (Throwable cause) { 
  12.             return cause; 
  13.         } 
  14.     } 
  15. }); 

这个代码是返回一个 SelectorImpl的Class对象,这里是返回SelectorImpl的Class对象!我们由上述代码可以看出来,如果获取失败,会返回一个异常,异常的话肯定不行,所以就要对可能会发生的异常做出操作:

  1. //如果没有获取成功 
  2. f (!(maybeSelectorImplClass instanceof Class) || 
  3.    // 确保当前的选择器实现是我们可以检测到的。  判断是 unwrappedSelector的子类或者同类 
  4.    !((Class<?>) maybeSelectorImplClass).isAssignableFrom(unwrappedSelector.getClass())) { 
  5.    //发生异常 
  6.    if (maybeSelectorImplClass instanceof Throwable) { 
  7.        Throwable t = (Throwable) maybeSelectorImplClass; 
  8.        logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, t); 
  9.    } 
  10.    //还是包装为未优化的选择器 
  11.    return new SelectorTuple(unwrappedSelector); 

如果发生了异常,或者获取的和原始选择器不是一个对象,就还使用原始选择器包装返回!

4. 创建一个优化后的selectKeys

  1. 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

  1. //开始进行反射替换 
  2. Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() { 
  3.     @Override 
  4.     public Object run() { 
  5.         try { 
  6.             //获取选择器事件中的 事件对象 selectedKeys的属性对象 
  7.             Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys"); 
  8.             //获取公开选择的密钥 
  9.             Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys"); 
  10.  
  11.             //java9且存在 Unsafe的情况下 直接替换内存空间的数据 
  12.             if (PlatformDependent.javaVersion() >= 9 && PlatformDependent.hasUnsafe()) { 
  13.                 // 让我们尝试使用sun.misc.Unsafe替换SelectionKeySet。 
  14.                 // 这使我们也可以在Java9 +中执行此操作,而无需任何额外的标志。 
  15.                 long selectedKeysFieldOffset = PlatformDependent.objectFieldOffset(selectedKeysField); 
  16.                 long publicSelectedKeysFieldOffset = 
  17.                     PlatformDependent.objectFieldOffset(publicSelectedKeysField); 
  18.  
  19.                 if (selectedKeysFieldOffset != -1 && publicSelectedKeysFieldOffset != -1) { 
  20.                     PlatformDependent.putObject( 
  21.                         unwrappedSelector, selectedKeysFieldOffset, selectedKeySet); 
  22.                     PlatformDependent.putObject( 
  23.                         unwrappedSelector, publicSelectedKeysFieldOffset, selectedKeySet); 
  24.                     return null
  25.                 } 
  26.                 // 如果没法直接替换内存空间的数据 就想办法用反射 
  27.             } 
  28.             //java8或者java9+上述未操作完成的 使用反射来替换 
  29.             Throwable cause = ReflectionUtil.trySetAccessible(selectedKeysField, true); 
  30.             if (cause != null) { 
  31.                 return cause; 
  32.             } 
  33.             cause = ReflectionUtil.trySetAccessible(publicSelectedKeysField, true); 
  34.             if (cause != null) { 
  35.                 return cause; 
  36.             } 
  37.             //开始进行替换  将我们创建的优化后的事件数组来反射的替换进选择器中 
  38.             selectedKeysField.set(unwrappedSelector, selectedKeySet); 
  39.             publicSelectedKeysField.set(unwrappedSelector, selectedKeySet); 
  40.             return null
  41.         } catch (NoSuchFieldException e) { 
  42.             return e; 
  43.         } catch (IllegalAccessException e) { 
  44.             return e; 
  45.         } 
  46.     } 
  47. }); 

代码虽然多,但是,逻辑比较简单!

  1. 首先获取SelectorImpl类对象的 selectedKeys属性和publicSelectedKeys属性!
  2. 判断使用的JDK版本是不是9以上,如果使用的9的话,直接操作JAVA的Unsafe对象操作系统的内存空间!有关Unsafe的介绍,再零拷贝章节介绍的很详细,可以复习零拷贝章节! 我们这里使用的JDK8
  3. 如果使用的是JDK8,使用反射,将我们创建出来的SelectedKeys的优化对象SelectedSelectionKeySet反射的替换进unwrappedSelector这个原始的选择器!

6. 包装选择器

  1. return new SelectorTuple(unwrappedSelector, new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet)); 

首先把unwrappedSelector选择器包装为 SelectedSelectionKeySetSelector包装类!

再把unwrappedSelector和SelectedSelectionKeySetSelector对应起来,包装Wie元组返回!

四、保存优化后的选择器和原始选择器

  1. this.selector = selectorTuple.selector; 
  2. this.unwrappedSelector = selectorTuple.unwrappedSelector; 

五、调用父类,创建队列

  1. super(parent, executor, false, newTaskQueue(queueFactory),  
  2.       newTaskQueue(queueFactory),rejectedExecutionHandler); 

首先,他会通过newTaskQueue构建两个队列 ,这两个队列是什么类型的呢?

上一节课我们分析过,queueFactory == null,所以会走如图分支代码,DEFAULT_MAX_PENDING_TASKS如果没有指定的话,默认是Integer.MAX,最小为16!我们进入到 该分支代码看一下,他创建的是一个什么队列:

  1. public static <T> Queue<T> newMpscQueue() { 
  2.     return Mpsc.newMpscQueue(); 

可以看出他创建的是一个Mpsc队列,他是一个多生产者,单消费者队列,是由jctools框架提供的,后续如果可以,我会具体对该队列进行一个讲解,我们到这里就知道,再创建NIOEventLoop的时候,向父类内部传递了两个Mpsc队列,我们继续回到主线:

进入到super(xxx)的源码中:

  1. protected SingleThreadEventLoop(EventLoopGroup parent, Executor executor, boolean addTaskWakesUp, Queue<Runnable> taskQueue, Queue<Runnable> tailTaskQueue, 
  2.                                     RejectedExecutionHandler rejectedExecutionHandler) { 
  3.     super(parent, executor, addTaskWakesUp, taskQueue, rejectedExecutionHandler); 
  4.     //保存一个 tailTasks  尾部队列 
  5.     tailTasks = ObjectUtil.checkNotNull(tailTaskQueue, "tailTaskQueue"); 

这里会保存一个队列,尾部队列,这个尾部队列,官方的意思是想对Netty 的运行状态做一些统计数据,例如任务循环的耗时、占用物理内存的大小等等,但是实际上应用tailTasks的场景极少,这里不做太多讲解!

我们继续跟进到super方法源码里面:

  1. //parent   线程执行器   false   mpsc队列    拒绝策略 
  2. protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor, 
  3.                                         boolean addTaskWakesUp, Queue<Runnable> taskQueue, 
  4.                                           RejectedExecutionHandler rejectedHandler) { 
  5.     super(parent); 
  6.     this.addTaskWakesUp = addTaskWakesUp; 
  7.     this.maxPendingTasks = DEFAULT_MAX_PENDING_EXECUTOR_TASKS; 
  8.     //保存线程执行器 
  9.     this.executor = ThreadExecutorMap.apply(executor, this); 
  10.     //创建一个队列 Mpscq,外部线程执行的时候使用这个队列(不是在EventLoop的线程内执行的时候)  newTaskQueue(queueFactory) 
  11.     this.taskQueue = ObjectUtil.checkNotNull(taskQueue, "taskQueue"); 
  12.     //保存拒绝策略 
  13.     this.rejectedExecutionHandler = ObjectUtil.checkNotNull(rejectedHandler, "rejectedHandler"); 

这里是进一步保存,将该NioEventLoop对应的线程执行器  、MpscQuerey任务队列、对应的拒绝策略保存起来!  大家再后续看到使用对应变量的代码千万不要觉得陌生哦!

总结

创建和保存了两个多生产者单消费者队列tailTasks和taskQueue

保存一个线程执行器executor

保存一个拒绝策略,该拒绝策略主要用于队列满了之后如何处理!

保存一个选择器生产者!

创建一个优化后的选择器,并进行保存!

将原始选择器和优化后的选择器进行保存!

 

责任编辑:武晓燕 来源: 源码学徒
相关推荐

2015-09-16 09:10:27

Java源码解析

2022-05-20 10:32:49

事件循环器事件队列鸿蒙

2020-12-01 15:00:20

Java 基础

2021-02-20 06:09:46

libtask协程锁机制

2012-11-06 11:07:59

jQueryJSjQuery框架

2021-07-09 06:48:30

注册源码解析

2024-01-18 08:31:22

go实现gorm框架

2016-12-15 09:44:31

框架Caffe源码

2022-02-14 14:47:11

SystemUIOpenHarmon鸿蒙

2013-03-05 09:16:33

MySQLInnodb

2021-03-24 07:16:57

RocketMQ源码解析Topic

2010-01-25 10:35:12

Android复选框

2022-04-09 15:26:46

Kubernetes删除操作源码解析

2021-10-20 07:18:50

开源轻量级缓存

2022-12-07 08:02:43

Spring流程IOC

2021-10-27 16:52:37

LayoutInfl源码解析

2022-12-16 08:31:37

调度线程池源码

2018-07-19 15:57:46

ViewStub源码方法

2017-07-04 17:09:10

Map环形缓冲区数据

2016-09-22 15:50:38

JavascriptRedux源码解析
点赞
收藏

51CTO技术栈公众号