Netty 学习(六):创建 NioEventLoopGroup 的核心源码说明
Netty 学习(六):创建 NioEventLoopGroup 的核心源码说明
作者: Grey
原文地址:
博客园:Netty 学习(六):创建 NioEventLoopGroup 的核心源码说明
CSDN:Netty 学习(六):创建 NioEventLoopGroup 的核心源码说明
基于 JDK 的 API 自己实现 NIO 编程,需要一个线程池来不断监听端口。接收到新连接之后,这条连接上数据的读写会在另外一个线程池中进行。
在 Netty 实现的服务端中, 有如下经典代码
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap b = new ServerBootstrap();
// 设置服务端的线程模型。
// bossGroup 负责不断接收新的连接,将新的连接交给 workerGroup 来处理。
b.group(bossGroup, workerGroup)
其中 bossGroup 对应的就是监听端口的线程池,在绑定一个端口的情况下,这个线程池里只有一个线程;workerGroup 对应的是连接的数据读写的线程。
通过 debug 并设置断点的方式,我们来查看下创建 NioEventLoopGroup 的核心过程,
在没有指定线程数的情况下new NioEventLoopGroup()会调用如下构造方法
public NioEventLoopGroup() {
this(0);
}
即传入 0,然后一路跟下去,发现调用了MultithreadEventLoopGroup的如下逻辑
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}
由于我们传入的nThreads == 0,所以获取DEFAULT_EVENT_LOOP_THREADS的值,在MultithreadEventLoopGroup中,DEFAULT_EVENT_LOOP_THREADS的初始化逻辑如下
private static final int DEFAULT_EVENT_LOOP_THREADS;
static {
DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
"io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
if (logger.isDebugEnabled()) {
logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
}
}
在nThreads == 0的情况下,那么 NioEventLoopGroup 的默认线程的个数为 CPU 的核数乘以 2,即:NettyRuntime.availableProcessors() * 2。
继续跟下去,可以看到 NioEventLoopGroup 调用了如下的构造方法,其核心代码如下
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
……
// 创建ThreadPerTaskExecutor:ThreadPerTaskExecutor表示每次调用execute()方法的时候,都会创建一个线程。
if (executor == null) {
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
……
// 2.创建NioEventLoop:NioEventLoop对应线程池里线程的概念,这里其实就是用一个for循环创建的。
children = new EventExecutor[nThreads];
……
for (int i = 0; i < nThreads; i ++) {
……
children[i] = newChild(executor, args);
……
}
// 3.创建线程选择器:线程选择器的作用是确定每次如何从线程池中选择一个线程,也就是每次如何从NioEventLoopGroup中选择一个NioEventLoop。
chooser = chooserFactory.newChooser(children);
……
}
这个构造方法包括了三个内容
创建 ThreadPerTaskExecutor:ThreadPerTaskExecutor 主要是用来创建线程。
创建 NioEventLoop:NioEventLoop 对应线程池里线程的概念。
创建线程选择器:线程选择器的作用是确定每次如何从线程池中选择一个线程,也就是每次如何从 NioEventLoopGroup 中选择一个 NioEventLoop。
首先,我们看 ThreadPerTaskExecutor 如何创建线程,核心代码如下
public final class ThreadPerTaskExecutor implements Executor {
private final ThreadFactory threadFactory;
public ThreadPerTaskExecutor(ThreadFactory threadFactory) {
this.threadFactory = ObjectUtil.checkNotNull(threadFactory, "threadFactory");
}
@Override
public void execute(Runnable command) {
threadFactory.newThread(command).start();
}
}
这里的 threadFactory 就是前面传入的newDefaultThreadFactory(),这个方法定义了默认线程的一些基本信息,一路追踪到DefaultThreadFactory中
public DefaultThreadFactory(String poolName, boolean daemon, int priority, ThreadGroup threadGroup) {
ObjectUtil.checkNotNull(poolName, "poolName");
if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
throw new IllegalArgumentException(
"priority: " + priority + " (expected: Thread.MIN_PRIORITY <= priority <= Thread.MAX_PRIORITY)");
}
prefix = poolName + '-' + poolId.incrementAndGet() + '-';
this.daemon = daemon;
this.priority = priority;
this.threadGroup = threadGroup;
}
// 创建线程,将 JDK 的 Runnable 包装成 FastThreadLocalRunnable
@Override
public Thread newThread(Runnable r) {
Thread t = newThread(FastThreadLocalRunnable.wrap(r), prefix + nextId.incrementAndGet());
try {
if (t.isDaemon() != daemon) {
t.setDaemon(daemon);
}
if (t.getPriority() != priority) {
t.setPriority(priority);
}
} catch (Exception ignored) {
// Doesn't matter even if failed to set.
}
return t;
}
可以看到 Netty 的线程实体是由 ThreadPerTaskExecutor 创建的,ThreadPerTaskExecutor 每次执行 execute 的时候都会创建一个 FastThreadLocalThread 的线程实体。
接下来是创建 NioEventLoop,Netty 使用 for 循环来创建 nThreads 个 NioEventLoop,通过前面的分析,我们可能已经猜到,一个NioEventLoop对应一个线程实体,即 Netty 自己封装的 FastThreadLocalThread。
来到 NioEventLoop 的构造方法
NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler,
EventLoopTaskQueueFactory taskQueueFactory, EventLoopTaskQueueFactory tailTaskQueueFactory) {
super(parent, executor, false, newTaskQueue(taskQueueFactory), newTaskQueue(tailTaskQueueFactory),
rejectedExecutionHandler);
......
final SelectorTuple selectorTuple = openSelector();
......
}
即创建了一个 Selector,Selector 是 NIO 编程里最核心的概念,一个 Selector 可以将多个连接绑定在一起,负责监听这些连接的读写事件,即多路复用。
继续往上调用构造方法
protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor,
boolean addTaskWakesUp, Queue<Runnable> taskQueue,
RejectedExecutionHandler rejectedHandler) {
......
this.taskQueue = ObjectUtil.checkNotNull(taskQueue, "taskQueue");
......
}
NioEventLoop 重写了 taskQueue 的创建逻辑
private static Queue<Runnable> newTaskQueue0(int maxPendingTasks) {
// This event loop never calls takeTask()
return maxPendingTasks == Integer.MAX_VALUE ? PlatformDependent.<Runnable>newMpscQueue()
: PlatformDependent.<Runnable>newMpscQueue(maxPendingTasks);
}
private static Queue<Runnable> newTaskQueue(
EventLoopTaskQueueFactory queueFactory) {
if (queueFactory == null) {
return newTaskQueue0(DEFAULT_MAX_PENDING_TASKS);
}
return queueFactory.newTaskQueue(DEFAULT_MAX_PENDING_TASKS);
}
即创建一个 MPSC 队列,
MPSC 队列,Selector,NioEventLoop,这三者均为一对一关系。
接下来是创建线程选择器,
chooser = chooserFactory.newChooser(children);
这里的选择器是
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
}
中的DefaultEventExecutorChooserFactory.INSTANCE,进入
private static boolean isPowerOfTwo(int val) {
return (val & -val) == val;
}
@Override
public EventExecutorChooser newChooser(EventExecutor[] executors) {
if (isPowerOfTwo(executors.length)) {
return new PowerOfTwoEventExecutorChooser(executors);
} else {
return new GenericEventExecutorChooser(executors);
}
}
Netty 通过判断 NioEventLoopGroup 中的 NioEventLoop 是否是2的幂来创建不同的线程选择器,不管是哪一种选择器,最终效果都是从第一个 NioEvenLoop 遍历到最后一个NioEventLoop,再从第一个开始,如此循环。GenericEventExecutorChooser 通过简单的累加取模来实现循环的逻辑,而 PowerOfTowEventExecutorChooser 是通过位运算实现的。
private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
......
@Override
public EventExecutor next() {
return executors[idx.getAndIncrement() & executors.length - 1];
}
......
}
private static final class GenericEventExecutorChooser implements EventExecutorChooser {
......
@Override
public EventExecutor next() {
return executors[(int) Math.abs(idx.getAndIncrement() % executors.length)];
}
......
}
最后总结一下,NioEventLoopGroup 的创建核心就三步
创建ThreadPerTaskExecutor;
创建NioEventLoop;
创建线程选择器。
完整代码见:hello-netty
本文所有图例见:processon: Netty学习笔记
更多内容见:Netty专栏
参考资料
Netty 学习(六):创建 NioEventLoopGroup 的核心源码说明的更多相关文章
- Java内存管理-掌握类加载器的核心源码和设计模式(六)
勿在流沙筑高台,出来混迟早要还的. 做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 上一篇文章介绍了类加载器分类以及类加载器的双亲委派模型,让我们能够从整体上对类加载器有 ...
- javaweb期末项目-stage2-项目创建、配置、接口设计和功能实现(含核心源码)
项目的创建.配置.接口设计和功能实现(含核心代码).rar--下载 说明:解压密码为袁老师的全名拼音(全小写) 相关链接: 项目结构:https://www.cnblogs.com/formyfish ...
- Android版数据结构与算法(五):LinkedHashMap核心源码彻底分析
版权声明:本文出自汪磊的博客,未经作者允许禁止转载. 上一篇基于哈希表实现HashMap核心源码彻底分析 分析了HashMap的源码,主要分析了扩容机制,如果感兴趣的可以去看看,扩容机制那几行最难懂的 ...
- iOS 开源库系列 Aspects核心源码分析---面向切面编程之疯狂的 Aspects
Aspects的源码学习,我学到的有几下几点 Objective-C Runtime 理解OC的消息分发机制 KVO中的指针交换技术 Block 在内存中的数据结构 const 的修饰区别 block ...
- HashMap的结构以及核心源码分析
摘要 对于Java开发人员来说,能够熟练地掌握java的集合类是必须的,本节想要跟大家共同学习一下JDK1.8中HashMap的底层实现与源码分析.HashMap是开发中使用频率最高的用于映射(键值对 ...
- 手撕spring核心源码,彻底搞懂spring流程
引子 十几年前,刚工作不久的程序员还能过着很轻松的日子.记得那时候公司里有些开发和测试的女孩子,经常有问题解决不了的,不管什么领域的问题找到我,我都能帮她们解决.但是那时候我没有主动学习技术的意识,只 ...
- 并发编程之 SynchronousQueue 核心源码分析
前言 SynchronousQueue 是一个普通用户不怎么常用的队列,通常在创建无界线程池(Executors.newCachedThreadPool())的时候使用,也就是那个非常危险的线程池 ^ ...
- 6 手写Java LinkedHashMap 核心源码
概述 LinkedHashMap是Java中常用的数据结构之一,安卓中的LruCache缓存,底层使用的就是LinkedHashMap,LRU(Least Recently Used)算法,即最近最少 ...
- 3 手写Java HashMap核心源码
手写Java HashMap核心源码 上一章手写LinkedList核心源码,本章我们来手写Java HashMap的核心源码. 我们来先了解一下HashMap的原理.HashMap 字面意思 has ...
随机推荐
- OS模块中获取当前文件的绝对路径的相关方法
os.path.realpath(__file__) 作用:获取当前执行py脚本的绝对路径(在当前工作目录下的绝对路径) __file__ : 表示当前文件的本身,一般值是当前文件的相对路径 例如: ...
- Springboot Jpa: [mysql] java.sql.SQLException: Duplicate entry 'XXX' for key 'PRIMARY'
前言 1.问题背景 偶尔会出现登录请求出错的情况,一旦失败就会短时间内再也登录不上,更换浏览器或者刷新可能会暂时解决这个问题. 项目运行日志如下: 2022-07-21 09:43:40.946 DE ...
- 红黑树以及JAVA实现(一)
目录 前言 一. B树 1.1 概念 1.2 2-3-4树 1.3 2-3-4树的插入 节点分类 1.4 2-3-4树的删除 1.4.1 当删除节点是叶子节点 1.4.1.1 当删除节点为非2节点 1 ...
- Git 05 文件操作
参考源 https://www.bilibili.com/video/BV1FE411P7B3?spm_id_from=333.999.0.0 版本 本文章基于 Git 2.35.1.2 文件的四种状 ...
- Spring 01 概述
简介 Spring 是开源的轻量级 J2EE 框架 我们常说的 Spring 实际上是指 Spring Framework,它是 Spring 家族中的一个重要分支. 官方文档 https://doc ...
- 长篇图解java反射机制及其应用场景
一.什么是java反射? 在java的面向对象编程过程中,通常我们需要先知道一个Class类,然后new 类名()方式来获取该类的对象.也就是说我们需要在写代码的时候(编译期或者编译期之前)就知道我们 ...
- "蔚来杯"2022牛客暑期多校训练营9 G Magic Spells【马拉车+哈希】
四川今天又上热搜了,继南部疫情的未雨绸缪后,龙槽沟是真的倾盆大雨了.我没有兴趣虚伪矫情地对罹难的游人表达同情,因为人与人互不相通徒增谈资:我也没有兴趣居高临下地对擅闯的愚人表达不屑,因为你我皆为乌合之 ...
- 使用Docker搭建Nextcloud私有网盘
一.准备材料 安装环境:linux 工具:docker 软件:MySql.Nextcloud 二.安装Docker 安装Docker:https://www.cnblogs.com/jzcn/p/15 ...
- screen -中断保留-屏幕同步
工作中经常用到 screen 用处: 中断保留 和屏幕同步. yum install screen screen -S name 创建 -ls 查看 -r 恢复 -x 同屏
- 弹簧高跷题解---双向DP---DD(XYX)的博客
三 . 弹簧高跷 时间限制: 1 Sec 内存限制: 128 MB 题目描述.输入.输出 ----------- 方法 这道题用DP是可以解决的.因为每一次跳跃都与前一次跳跃有关, ...