netty(3)—源码NioEventLoopGroup

一、概念

NioEventLoopGroup对象可以理解为一个线程池,内部维护了一组线程,每个线程负责处理多个Channel上的事件,而一个Channel只对应于一个线程,这样可以回避多线程下的数据同步问题。

我们先回顾下 上篇博客的服务器代码

  1. // 定义一对线程组
  2. // 主线程组, 用于接受客户端的连接,但是不做任何处理,跟老板一样,不做事
  3. EventLoopGroup bossGroup = new NioEventLoopGroup();
  4. // 从线程组, 老板线程组会把任务丢给他,让手下线程组去做任务
  5. EventLoopGroup workerGroup = new NioEventLoopGroup();
  6. // netty服务器的创建, 辅助工具类,用于服务器通道的一系列配置
  7. ServerBootstrap serverBootstrap = new ServerBootstrap();
  8. serverBootstrap.group(bossGroup, workerGroup) //绑定两个线程组
  9. //省略......

职责:

  1. 作为服务端 Acceptor 线程,负责处理客户端的请求接入。
  2. 作为客户端 Connector 线程,负责注册监听连接操作位,用于判断异步连接结果。
  3. 作为 IO 线程,监听网络读操作位,负责从 SocketChannel 中读取报文。
  4. 作为 IO 线程,负责向 SocketChannel 写入报文发送给对方,如果发生写半包,会自动注册监听写事件,用 于后续继续发送半包数据,直到数据全部发送完成。
  5. 作为定时任务线程,可以执行定时任务,例如链路空闲检测和发送心跳消息等。
  6. 作为线程执行器可以执行普通的任务线程(Runnable)。

二、NioEventLoopGroup源码分析

上面的代码 创建bossGroup及workerGroup时,使用了NioEventLoopGroup的无参构造方法,本篇将从此无参构造入手,详细分析NioEventLoopGroup的初始化过程。

  1. /**
  2. * 1、首先我们看看NioEventLoopGroup的无参构造方法:
  3. * 作用:线程数为0
  4. */
  5. public NioEventLoopGroup() {
  6. this(0);
  7. }
  8. /**
  9. * 2、继续调用构造函数。
  10. * 作用:指定线程为0,且Executor为null
  11. */
  12. public NioEventLoopGroup(int nThreads) {
  13. this(nThreads, (Executor) null);
  14. }
  15. /**
  16. * 3、继续调用构造函数
  17. * 作用:此构造方法它会指定selector的辅助类 "SelectorProvider.provider()"
  18. */
  19. public NioEventLoopGroup(int nThreads, Executor executor) {
  20. this(nThreads, executor, SelectorProvider.provider());
  21. }
  22. /**
  23. * 4、继续调用构造函数
  24. * 作用:初始化了一个默认的选择策略工厂,用于生成select策略
  25. */
  26. public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider) {
  27. this(nThreads, executor, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);
  28. }
  29. /**
  30. * 5、继续调用构造函数
  31. * 作用:指定拒绝策略:RejectedExecutionHandlers.reject()
  32. */
  33. public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider,final SelectStrategyFactory selectStrategyFactory) {
  34. super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
  35. }

经过上面一系列的构造方法调用,此时参数值对应如下:

  1. nThreads: 0
  2. executor: null
  3. selectorProvider: SelectorProvider.provider()
  4. selectStrategyFactory: DefaultSelectStrategyFactory.INSTANCE
  5. 以及指定了拒绝策略: RejectedExecutionHandlers.reject()
  1. /**
  2. * 6、从这里开始 调用父类 MultithreadEventLoopGroup 的构造函数
  3. * 作用: 就是当指定的线程数为0时,使用默认的线程数DEFAULT_EVENT_LOOP_THREADS,
  4. * 而DEFAULT_EVENT_LOOP_THREAD是在静态代码块中就被执行。
  5. */
  6. protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
  7. super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
  8. }
  9. /**
  10. * 6.1 我们看下静态代码块
  11. * 作用:到这一步得出关键的一点:`如果初始化NioEventLoopGroup未指定线程数,默认是CPU核心数*2`。
  12. */
  13. private static final int DEFAULT_EVENT_LOOP_THREADS;
  14. static {
  15. DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
  16. "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2))
  17. }
  18. /**
  19. * 7、继续调用父类 MultithreadEventLoopGroup 构造函数
  20. * 作用:指定了一个EventExecutor的选择工厂DefaultEventExecutorChooserFactory,
  21. * 此工厂主要是用于选择下一个可用的EventExecutor
  22. */
  23. protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
  24. this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
  25. }
  26. /**
  27. * 8、继续调用父类 MultithreadEventLoopGroup 构造函数 这里就是核心代码 删除部分非核心代码
  28. * 作用单独分析
  29. */
  30. protected MultithreadEventExecutorGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory, Object... args) {
  31. //1、
  32. //executor校验非空, 如果为空就创建ThreadPerTaskExecutor, 该类实现了 Executor接口
  33. // 这个executor 是用来执行线程池中的所有的线程,也就是所有的NioEventLoop,其实从
  34. //NioEventLoop构造器中也可以知道,NioEventLoop构造器中都传入了executor这个参数。
  35. if (executor == null) {
  36. executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
  37. }
  38. //2、
  39. //这里的children数组, 其实就是线程池的核心实现,线程池中就是通过指定的线程数组来实现线程池;
  40. //数组中每个元素其实就是一个EventLoop,EventLoop是EventExecutor的子接口。
  41. children = new EventExecutor[nThreads];
  42. //for循环实例化children数组,NioEventLoop对象
  43. for (int i = 0; i < nThreads; i++) {
  44. boolean success = false;
  45. //3、
  46. //newChild(executor, args) 函数在NioEventLoopGroup类中实现了,
  47. // 实质就是就是存入了一个 NIOEventLoop类实例
  48. children[i] = newChild(executor, args);
  49. success = true;
  50. }
  51. //4、实例化线程工厂执行器选择器: 根据children获取选择器
  52. chooser = chooserFactory.newChooser(children);
  53. //5、为每个EventLoop线程添加 线程终止监听器
  54. final FutureListener<Object> terminationListener = new FutureListener<Object>() {
  55. @Override
  56. public void operationComplete(Future<Object> future) throws Exception {
  57. if (terminatedChildren.incrementAndGet() == children.length) {
  58. terminationFuture.setSuccess(null);
  59. }
  60. }
  61. };
  62. //6、将children 添加到对应的set集合中去重, 表示只可读。
  63. Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
  64. Collections.addAll(childrenSet, children);
  65. readonlyChildren = Collections.unmodifiableSet(childrenSet);
  66. }
  67. }
  68. /**
  69. * 8.3.1 我们再来看下 newChild(executor, args) 里的方法
  70. * 我们可以看到 返回的就是一个 NioEventLoop
  71. */
  72. @Override
  73. protected EventLoop newChild(Executor executor, Object... args) throws Exception {
  74. return new NioEventLoop(this, executor, (SelectorProvider) args[0],
  75. ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);
  76. }

我们再回顾总结一下:

  1. 1. NioEventLoopGroup初始化时未指定线程数,那么会使用默认线程数,即 `线程数 = CPU核心数 * 2`
  2. 2. 每个NioEventLoopGroup对象内部都有一组可执行的`NioEventLoop数组`,其大小是 nThreads, 这样就构成了一个线程池, `一个NIOEventLoop可以理解成就是一个线程`
  3. 3. 所有的NIOEventLoop线程是使用相同的 executorSelectorProviderSelectStrategyFactoryRejectedExecutionHandler以及是属于某一个
  4. NIOEventLoopGroup的。这一点从 newChild(executor, args); 方法就可以看出:newChild()的实现是在NIOEventLoopGroup中实现的。
  5. 4. 当有IO事件来时,需要从线程池中选择一个线程出来执行,这时候的NioEventLoop选择策略是由GenericEventExecutorChooser实现的,并调用该类的next()方法。
  6. 5. 每个NioEventLoopGroup对象都有一个NioEventLoop选择器与之对应,其会根据NioEventLoop的个数,动态选择chooser(如果是2的幂次方,则按位运算,否则使用普通的轮询)

所以通过上面的分析,我们得出NioEventLoopGroup主要功能就是为了创建一定数量的NioEventLoop,而真正的重点就在NioEventLoop中,它是整个netty线程执行的关键。

有关NioEventLoop可以参考文章: [netty源码分析]--EventLoopGroup与EventLoop

​ 相关文章推荐:netty服务端源码分析之eventloop和eventloopgroup

  1. 如果一个人充满快乐,正面的思想,那么好的人事物就会和他共鸣,而且被他吸引过来。同样,一个人老带悲伤,倒霉的事情也会跟过来。
  2. ——在自己心情低落的时候,告诫自己不要把负能量带给别人。(大校12

【Netty】(3)—源码NioEventLoopGroup的更多相关文章

  1. Netty 4源码解析:请求处理

    Netty 4源码解析:请求处理 通过之前<Netty 4源码解析:服务端启动>的分析,我们知道在最前端"扛压力"的是NioEventLoop.run()方法.我们指定 ...

  2. Netty 4源码解析:服务端启动

    Netty 4源码解析:服务端启动 1.基础知识 1.1 Netty 4示例 因为Netty 5还处于测试版,所以选择了目前比较稳定的Netty 4作为学习对象.而且5.0的变化也不像4.0这么大,好 ...

  3. Netty(6)源码-服务端与客户端创建

    原生的NIO类图使用有诸多不便,Netty向用户屏蔽了细节,在与用户交界处做了封装. 一.服务端创建时序图 步骤一:创建ServerBootstrap实例 ServerBootstrap是Netty服 ...

  4. 我为 Netty 贡献源码 | 且看 Netty 如何应对 TCP 连接的正常关闭,异常关闭,半关闭场景

    欢迎关注公众号:bin的技术小屋,本文图片加载不出来的话可查看公众号原文 本系列Netty源码解析文章基于 4.1.56.Final版本 写在前面..... 本文是笔者肉眼盯 Bug 系列的第三弹,前 ...

  5. Netty(7)源码-ByteBuf

    一.ByteBuf工作原理 1. ByteBuf是ByteBuffer的升级版: jdk中常用的是ByteBuffer,从功能角度上,ByteBuffer可以完全满足需要,但是有以下缺点: ByteB ...

  6. netty下载源码并导入idea

    netty源码导入eclipse会有一些兼容性问题,网上有解决方案,官方推荐idea,故此用idea. 拷贝git地址:https://github.com/netty/netty.git 使用git ...

  7. Netty ByteBuf源码分析

    Netty的ByteBuf是JDK中ByteBuffer的升级版,提供了NIO buffer和byte数组的抽象视图. ByteBuf的主要类集成关系: (图片来自Netty权威指南,图中有一个画错的 ...

  8. 【Netty】源码分析目录

    前言 为方便系统的学习Netty,特整理文章目录如下. [Netty]第一个Netty应用 [Netty]Netty核心组件介绍 [Netty]Netty传输 [Netty]Netty之ByteBuf ...

  9. netty UnpooledHeapByteBuf 源码分析

    UnpooledHeapByteBuf 是基于堆内存进行内存分配的字节缓冲区,没有基于对象池技术实现,这意味着每次I/O的读写都会创建一个新的UnpooledHeapByteBuf,频繁进行大块内存的 ...

随机推荐

  1. 最新的爬虫工具requests-html

    使用Python开发的同学一定听说过Requsts库,它是一个用于发送HTTP请求的测试.如比我们用Python做基于HTTP协议的接口测试,那么一定会首选Requsts,因为它即简单又强大.现在作者 ...

  2. 随手一记,maven打包

    <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-depen ...

  3. BZOJ_1954_Pku3764 The xor-longest Path_Trie树

    Description 给定一棵n个点的带权树,求树上最长的异或和路径 把根到点路径上点权异或和求出来,然后变成了Trie树裸题.   代码: #include <cstdio> #inc ...

  4. Centos 7 Linux系统修改网卡名称为ethx

    一.Centos7 系统安装完成后更改网卡名称方法 1.查看Centos7系统默认的网卡配置(eno16777736) [root@server ~]# ifconfig eno16777736: f ...

  5. java基于BasicPlayer调用 播放音乐

    无聊中想想用java调用下听音乐的api.晚上很多文章用的比较老大方法了,都是用原生的代码写,而且不支持mp3格式,BasicPlayer第三方包提供了很好的api调用,简单的3行代码就可以调用mp3 ...

  6. hystrix 请求合并(6)

    hystrix支持N个请求自动合并为一个请求,这个功能在有网络交互的场景下尤其有用,比如每个请求都要网络访问远程资源,如果把请求合并为一个,将使多次网络交互变成一次,极大节省开销.重要一点,两个请求能 ...

  7. 用Python学分析 - 二项分布

    二项分布(Binomial Distribution)对Bernoulli试验序列的n次序列,结局A出现的次数x的概率分布服从二项分布- 两分类变量并非一定会服从二项分布- 模拟伯努利试验中n次独立的 ...

  8. .NET Core 迁移躺坑记续集--Win下莫名其妙的超时

    继上一集里说到遇到的各种问题并且弄了n个解决方案之后,特别是对于问题4的解决方案对于切换了HttpClientFactory 我用了你家netcore 2.1下专门解决之前HttpClient口病已久 ...

  9. 深度学习之注意力机制(Attention Mechanism)和Seq2Seq

    这篇文章整理有关注意力机制(Attention Mechanism )的知识,主要涉及以下几点内容: 1.注意力机制是为了解决什么问题而提出来的? 2.软性注意力机制的数学原理: 3.软性注意力机制. ...

  10. Spark学习之编程进阶总结(二)

    五.基于分区进行操作 基于分区对数据进行操作可以让我们避免为每个数据元素进行重复的配置工作.诸如打开数据库连接或创建随机数生成器等操作,都是我们应当尽量避免为每个元素都配置一次的工作.Spark 提供 ...