Netty源码学习系列之1-NioEventLoopGroup的初始化
前言
NioEventLoopGroup是netty对Reactor线程组这个抽象概念的具体实现,其内部维护了一个EventExecutor数组,而NioEventLoop就是EventExecutor的实现(看名字也可发现,一个是NioEventLoopGroup,一个是NioEventLoop,前者是集合,后者是集合中的元素)。一个NioEventLoop中运行着唯一的一个线程即Reactor线程,这个线程一直执行NioEventLoop的run方法。这个run方法就是netty的核心方法,其重要性可以类比于Spring中的refresh方法。
下面是从百度上随便找的一篇netty文章的线程模型图(详见文章https://www.cnblogs.com/luoxn28/p/11875340.html),此处引用是为方便在头脑中产生一个整体印象,结合图下面的代码进行各个概念的归位。图中绿色的Reactor Thread就是上文说的NioEventLoopGroup,对应下面代码中的boss变量,负责处理客户端的连接事件,它其实也是一个池(因为内部维护的是一个数组);蓝色的Reactor Thread Pool也是NioEventLoopGroup,对应下面代码中的worker变量,负责处理客户端的读写事件。

注:上图是Reactor多线程模型,而下面的代码示例是主从多线程模型,区别是只要将代码boss中的参数2改成1,示例代码就成了多线程模型,细细品味一下。
public class NettyDemo1 {
// netty服务端的一般性写法
public static void main(String[] args) {
EventLoopGroup boss = new NioEventLoopGroup(2);
EventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boss, worker).channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(new NettyServerHandler());
}
});
ChannelFuture channelFuture = bootstrap.bind(90);
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
以上部分是博主对netty的一个概括性总结,以将概念和其实现连接起来,方便建立一个初始的总体认识,下面进入EventLoopGroup的初始化。
一、EventLoopGroup初始化
1、NioEventLoopGroup构造器
顺着有参和无参的构造方法进去,发现无参的构造器将线程数赋值0继续调了有参的构造器,而有参的构造器将线程池executor参数赋值null继续调重载构造器
public NioEventLoopGroup() {
this(0);
}
public NioEventLoopGroup(int nThreads) {
this(nThreads, (Executor) null);
}
public NioEventLoopGroup(int nThreads, Executor executor) {
this(nThreads, executor, SelectorProvider.provider());
}
因为博主是在笔记本电脑调试的,故此时的selectorProvider是WindowsSelectorProvider,然后又加了一个参数DefaultSelectStrategyFactory单例对象:
public NioEventLoopGroup(
int nThreads, Executor executor, final SelectorProvider selectorProvider) {
this(nThreads, executor, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);
}
然后调父类的构造器,在末尾增加一个参数RejectedExecutionHandler单例对象:
public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider,
final SelectStrategyFactory selectStrategyFactory) {
super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
}
2、MultithreadEventLoopGroup构造器
在该构造器中,对线程数参数进行了处理,如果是0(对应上面NioEventLoopGroup的无参构造器),则将线程数设置为默认值,默认值取的是CPU核数*2,8核处理器对应16个线程;如果不是0,则以指定的线程数为准。同时,将executor后面的参数变为数组的形式,对应上面可以知道args中有三个元素:WindowsSelectorProvider、DefaultSelectStrategyFactory、RejectedExecutionHandler。
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}
3、MultithreadEventExecutorGroup构造器
此构造器又在args数组前面加了一个单例对象DefaultEventExecutorChooserFactory,用于从NioEventLoopGroup的数组中选取一个NioEventLoop。
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
}
下面才是最终的核心构造器方法,结合注释应该比较好理解。其中最重要的是第3步和第4步,下面着重讲解这两步。
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
// 1.对线程数进行校验
if (nThreads <= 0) {
throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
}
// 2.给线程池参数赋值,从前面追踪可知,若未赋值,executor一直是null,后续用于创建NioEventLoop中的启动线程,所以这玩意就是一个线程工厂
if (executor == null) {
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
// 3.给children循环赋值,newChild方法是重点,后续会讲解 ***
children = new EventExecutor[nThreads];
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
try {
children[i] = newChild(executor, args);
success = true;
} catch (Exception e) {
// TODO: Think about if this is a good exception type
throw new IllegalStateException("failed to create a child event loop", e);
} finally {
// 省略掉未创建成功后的资源释放处理
}
}
// 4.完成chooser选择器的赋值,此处是netty一个小的优化点,后续会讲解 **
chooser = chooserFactory.newChooser(children);
// 5.给数组中每一个成员设置监听器处理
final FutureListener<Object> terminationListener = new FutureListener<Object>() {
@Override
public void operationComplete(Future<Object> future) throws Exception {
if (terminatedChildren.incrementAndGet() == children.length) {
terminationFuture.setSuccess(null);
}
}
}; for (EventExecutor e: children) {
e.terminationFuture().addListener(terminationListener);
}
// 6.设置一个只读的set集合
Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
Collections.addAll(childrenSet, children);
readonlyChildren = Collections.unmodifiableSet(childrenSet);
}
3.1)、第4步chooser的赋值
由上面构造器调用过程可知,chooserFactory对应DefaultEventExecutorChooserFactory对象,该对象的newChooser方法如下:
public EventExecutorChooser newChooser(EventExecutor[] executors) {
if (isPowerOfTwo(executors.length)) {
return new PowerOfTwoEventExecutorChooser(executors);
} else {
return new GenericEventExecutorChooser(executors);
}
}
逻辑比较简单,判断数组的长度是不是2的N次幂,如果是,返回PowerOfTwoEventExecutorChooser对象,如果不是则返回GenericEventExecutorChooser对象。这二者有什么区别,netty设计者为什么要这么做呢?如果对HashMap的实现原理有深入了解的园友应该不难想到,如果一个数X是2的N次幂,那么用任意一个数Y对X取模可以用Y&(X-1)来高效的完成,这样做比直接%取模快了好几倍,这也是HashMap用2次幂作为数组长度的主要原因。这里是同样的道理,如下代码所示,这两个chooser类都很简单,内部维护了一个原子递增对象,每次调用next方法都加1,然后用这个数与数组长度取模,得到要对应下标位置的元素。而如果数组长度刚好是2次幂,用PowerOfTwoEventExecutorChooser就会提高效率,如果不是那也没办法,走%取模就是了。netty这种对效率提升的处理,是否在平时的CRUD中也能套用一下呢?
private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
private final AtomicInteger idx = new AtomicInteger();
private final EventExecutor[] executors;
PowerOfTwoEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}
@Override
public EventExecutor next() {
return executors[idx.getAndIncrement() & executors.length - 1];
}
}
private static final class GenericEventExecutorChooser implements EventExecutorChooser {
private final AtomicInteger idx = new AtomicInteger();
private final EventExecutor[] executors;
GenericEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}
@Override
public EventExecutor next() {
return executors[Math.abs(idx.getAndIncrement() % executors.length)];
}
}
3.2)、第3步newChild方法的逻辑
该方法的实现在NioEventLoopGroup中,由于args长度为3,所以queueFactory为null(暂时未发现哪里的实现args参数长度会是4,或许只是为后续扩展用,如果园友对args长度为4的场景有了解的还请留言指教)。然后调用了NioEventLoop的构造器,下面进入NioEventLoop的初始化。
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
EventLoopTaskQueueFactory queueFactory = args.length == 4 ? (EventLoopTaskQueueFactory) args[3] : null;
return new NioEventLoop(this, executor, (SelectorProvider) args[0],
((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2], queueFactory);
}
执行完上述初始化方法后NioEventLoopGroup的快照图如下,最重要的就两个属性:child和chooser。

二、NioEventLoop的初始化
1、NioEventLoop的构造器
到这里,有必要将此构造器的入参再梳理一遍。parent即上面的NioEventLoopGroup对象,executor是在MultithreadEventExecutorGroup中初始化的ThreadPerTaskExecutor,selectorProvider是WindowsSelectorProvider,strategy是DefaultSelectStrategyFactory,rejectedExecutionHandler是RejectedExecutionHandler,queueFactory是null。
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;
final SelectorTuple selectorTuple = openSelector();
selector = selectorTuple.selector;// netty封装的selector
unwrappedSelector = selectorTuple.unwrappedSelector;// java NIO原生的selector
selectStrategy = strategy;
}
可以看到只是做了一些赋值,其中newTaskQueue方法创建的是MpscUnboundedArrayQueue队列(多生产单消费无界队列,mpsc是multi provider single consumer的首字母缩写,即多个生产一个消费),继续追查父类构造方法。
2、SingleThreadEventLoop构造器
调用父类构造器,给tailTasks赋值。
protected SingleThreadEventLoop(EventLoopGroup parent, Executor executor,
boolean addTaskWakesUp, Queue<Runnable> taskQueue, Queue<Runnable> tailTaskQueue,
RejectedExecutionHandler rejectedExecutionHandler) {
super(parent, executor, addTaskWakesUp, taskQueue, rejectedExecutionHandler);
tailTasks = ObjectUtil.checkNotNull(tailTaskQueue, "tailTaskQueue");
}
3、SingleThreadEventExecutor构造器
在该构造方法中完成了剩余变量的赋值,其中有两个变量很重要:executor和taskQueue。前者负责创建Reactor线程,后者是实现串行无锁化的任务队列。
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);
this.taskQueue = ObjectUtil.checkNotNull(taskQueue, "taskQueue");
rejectedExecutionHandler = ObjectUtil.checkNotNull(rejectedHandler, "rejectedHandler");
}
NioEventLoopGroup的对象引用最终记录在了AbstractEventExecutor中:
protected AbstractEventExecutor(EventExecutorGroup parent) {
this.parent = parent;
}
NioeventLoop初始化完成之后的对象快照如下,左边是子类,右边是父类:

小结
本文详细讲述了netty中Reactor线程组概念模型的实现类 -- NioEventLoopGroup的实例化过程。NioEventLoopGroup和其内部数组元素NioEventLoop是netty通信框架的基石,相信本文的内容对初学netty的园友有一点帮助。
下篇将研究ServerBootstrap的初始化过程,敬请期待。
Netty源码学习系列之1-NioEventLoopGroup的初始化的更多相关文章
- Netty源码学习系列之4-ServerBootstrap的bind方法
前言 今天研究ServerBootstrap的bind方法,该方法可以说是netty的重中之重.核心中的核心.前两节的NioEventLoopGroup和ServerBootstrap的初始化就是为b ...
- Netty源码学习系列之1-netty的串行无锁化
前言 最近趁着跟老东家提离职之后.到新公司报道之前的这段空闲时期,着力研究了一番netty框架,对其有了一些浅薄的认识,后续的几篇文章会以netty为主,将近期所学记录一二,也争取能帮未对netty有 ...
- Netty源码学习系列之5-NioEventLoop的run方法
前言 NioEventLoop的run方法,是netty中最核心的方法,没有之一.在该方法中,完成了对已注册的channel上来自底层操作系统的socket事件的处理(在服务端时事件包括客户端 ...
- Netty源码学习(二)NioEventLoopGroup
0. NioEventLoopGroup简介 NioEventLoopGroup可以理解为一个线程池,内部维护了一组线程,每个线程负责处理多个Channel上的事件,而一个Channel只对应于一个线 ...
- Netty源码学习系列之2-ServerBootstrap的初始化
前言 根据前文我们知道,NioEventLoopGroup和NioEventLoop是netty对Reactor线程模型的实现,而本文要说的ServerBootstrap是对上面二者的整合与调用,是一 ...
- Netty 源码学习——EventLoop
Netty 源码学习--EventLoop 在前面 Netty 源码学习--客户端流程分析中我们已经知道了一个 EventLoop 大概的流程,这一章我们来详细的看一看. NioEventLoopGr ...
- Netty 源码学习——客户端流程分析
Netty 源码学习--客户端流程分析 友情提醒: 需要观看者具备一些 NIO 的知识,否则看起来有的地方可能会不明白. 使用版本依赖 <dependency> <groupId&g ...
- Netty 源码分析系列(二)Netty 架构设计
前言 上一篇文章,我们对 Netty做了一个基本的概述,知道什么是Netty以及Netty的简单应用. Netty 源码分析系列(一)Netty 概述 本篇文章我们就来说说Netty的架构设计,解密高 ...
- JDK源码学习系列05----LinkedList
JDK源码学习系列05----LinkedList 1.LinkedList简介 LinkedList是基于双向链表实 ...
随机推荐
- php接口开发经验总结
接口开发采用jwt方式验证: 接口开发最好留多几个返回方式,方便本地调用: 待更新.............
- java基础篇 之 集合概述(List)
list,有序集合,元素可重复 LinkedList:底层用链表实现,查找慢,增删快.为什么?? ArrayList:底层用数组实现,查找看,增删慢.为什么?? Vector:跟ArrayList一样 ...
- 【HBase】通过Java代码实现HBase数据库中数据的增删改查
目录 创建maven工程,导入jar包 java代码实现创建hbase表 java代码实现向hbase表中插入数据 java代码查询hbase数据 使用rowKey查询指定列族指定列的值 通过star ...
- SAP登录消息提醒
1功能说明 在相应用户登录时,给其提示相关信息. 2功能实现 2.1函数实现 在函数NAVIGATION_SET_START_TCODE中注册要监听的用户和程序的事务代码,当用户登录时,将自动运行 ...
- 值得收藏的js原型详解
从虚无到Object 起初,地是空虚混沌,渊面黑暗:这时候一切还是null 神说,要有原型,于是就有了prototype 原型从凭空产生,于是需要一个指向于null的特征,人们把这种特征叫做隐式原型, ...
- uCOS2014.1.11
typedef unsigned char BOOLEAN;typedef unsigned char INT8U; /* Unsigned 8 bit quantity */ty ...
- thread模块—Python多线程编程
Thread 模块 *注:在实际使用过程中不建议使用 thread 进行多线程编程,本文档只为学习(或熟悉)多线程使用. Thread 模块除了派生线程外,还提供了基本的同步数据结构,称为锁对象(lo ...
- Struts2-Tiles 2.5.2 升级指南和通配符拓展
最近工程从Struts2.3.18升级Struts2.5.2导致相关联的插件都需要升级到相同版本,其中tiles的变化最大. 1.web.xml上 listener org.apache.struts ...
- 一篇文章教会你利用Python网络爬虫获取电影天堂视频下载链接
[一.项目背景] 相信大家都有一种头疼的体验,要下载电影特别费劲,对吧?要一部一部的下载,而且不能直观的知道最近电影更新的状态. 今天小编以电影天堂为例,带大家更直观的去看自己喜欢的电影,并且下载下来 ...
- 使用vue实现购物车功能
页面效果图: html代码: <div class="shop-car" id='car'> <div class="count-custom" ...