NioEventLoopGroup 源码深度解析
一、什么是 NioEventLoopGroup?
在学习 Netty 的时候,我们经常会看到这样的代码:
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
那么 NioEventLoopGroup 到底是什么呢?
简单来说,NioEventLoopGroup 是 Netty 中的线程池实现。但它和我们熟悉的 ThreadPoolExecutor 不太一样,它是专门为事件驱动的 NIO 模型设计的线程池。
1.1 用一个比喻来理解
你可以把 NioEventLoopGroup 想象成一个餐厅的服务员团队:
- NioEventLoopGroup = 整个服务员团队(线程池)
- NioEventLoop = 每个服务员(单个线程)
- Channel = 每张餐桌(客户端连接)
当有客户(客户端)来用餐时,餐厅经理(Chooser)会分配一个服务员(NioEventLoop)来负责这张桌子。这个服务员会一直服务这张桌子,直到客户离开。
1.2 NioEventLoopGroup 的核心特点
- 固定数量的线程:创建时就确定了有多少个 NioEventLoop(线程)
- 每个线程独立工作:每个 NioEventLoop 有自己的任务队列和 Selector
- 负载均衡:通过 Chooser 将新连接分配给不同的 NioEventLoop
- 线程绑定:一个 Channel 绑定到一个 NioEventLoop 后就不会改变
二、NioEventLoopGroup 的构造方法
打开 NioEventLoopGroup 的源码,可以看到它有多个构造方法。作为初学者,我们只需要关注最常用的几个:
2.1 常用的构造方法
// 1. 无参构造(最常用)
public NioEventLoopGroup() {
this(0); // 传入 0,会使用默认线程数
}
// 2. 指定线程数(推荐使用)
public NioEventLoopGroup(int nThreads) {
this(nThreads, (Executor) null);
}
// 3. 指定线程数和 Executor(高级用法)
public NioEventLoopGroup(int nThreads, Executor executor) {
this(nThreads, executor, SelectorProvider.provider());
}
// 4. 指定线程数、Executor 和 SelectorProvider(更高级)
public NioEventLoopGroup(int nThreads, Executor executor,
final SelectorProvider selectorProvider) {
this(nThreads, executor, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);
}
// 5. 参数最全的构造方法(源码内部使用)
public NioEventLoopGroup(int nThreads, Executor executor,
final SelectorProvider selectorProvider,
final SelectStrategyFactory selectStrategyFactory) {
super(nThreads, executor, selectorProvider, selectStrategyFactory,
RejectedExecutionHandlers.reject());
}
2.2 构造参数详解
看到这么多参数,是不是有点懵?别担心,我们一个一个来理解:
| 参数名 | 作用 | 通俗解释 |
|---|---|---|
| nThreads | 线程数量 | 决定创建多少个 NioEventLoop(服务员数量) |
| Executor | 线程创建器 | 注意:不是线程池!只是用来创建线程的工具 |
| EventExecutorChooserFactory | 线程选择器工厂 | 决定如何从线程池中选择一个线程(如何分配服务员) |
| SelectorProvider | Selector 提供者 | 为每个 NioEventLoop 创建 Selector(NIO 的核心组件) |
| SelectStrategy | 选择策略 | 决定是阻塞等待还是立即返回 |
| RejectedExecutionHandler | 拒绝策略 | 当任务队列满了怎么办 |
| children 数组 | 核心数据结构 | 存储所有 NioEventLoop 实例的数组 |
2.3 初学者应该怎么用?
对于初学者来说,记住这两种用法就够了:
// 方式1:使用默认线程数(CPU核心数 * 2)
NioEventLoopGroup group = new NioEventLoopGroup();
// 方式2:指定线程数(推荐)
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1); // Boss 只需要 1 个线程
NioEventLoopGroup workerGroup = new NioEventLoopGroup(4); // Worker 可以多个线程
三、NioEventLoopGroup 的初始化过程
现在我们来看看,当你 new NioEventLoopGroup() 的时候,Netty 内部到底做了什么。
整个初始化过程可以分为 7 个步骤,我们一步一步来看:
步骤1:确定线程数量(nThreads)
// 在 MultithreadEventLoopGroup 中
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
// 如果 nThreads 为 0,使用默认值
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}
// DEFAULT_EVENT_LOOP_THREADS 的计算逻辑
static {
DEFAULT_EVENT_LOOP_THREADS = Math.max(1,
SystemPropertyUtil.getInt("io.netty.eventLoopThreads",
NettyRuntime.availableProcessors() * 2));
}
通俗解释:
- 如果你传入 0 或者不传参数,Netty 会自动计算线程数
- 默认线程数 =
CPU 核心数 × 2 - 比如你的电脑是 4 核 CPU,那么默认会创建 8 个线程
- 你也可以通过系统属性
io.netty.eventLoopThreads来指定
为什么是 CPU 核心数 × 2?
因为 NIO 是 IO 密集型操作,线程经常会阻塞在 IO 上,所以可以创建比 CPU 核心数更多的线程来提高吞吐量。
步骤2:创建线程创建器(Executor)
// 在 MultithreadEventExecutorGroup 中
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
// 如果没有传入 executor,创建默认的 ThreadPerTaskExecutor
this(nThreads, executor == null ? null : new ThreadPerTaskExecutor(executor), args);
}
// 如果 executor 为 null,会创建默认的
if (executor == null) {
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
ThreadPerTaskExecutor 的实现:
public final class ThreadPerTaskExecutor implements Executor {
private final ThreadFactory threadFactory;
public ThreadPerTaskExecutor(ThreadFactory threadFactory) {
this.threadFactory = threadFactory;
}
@Override
public void execute(Runnable command) {
// 每次执行任务都创建一个新线程
threadFactory.newThread(command).start();
}
}
通俗解释:
Executor不是线程池,它只是一个线程创建器ThreadPerTaskExecutor的作用是:每次需要线程时,就创建一个新线程- 这个 Executor 会为每个 NioEventLoop 创建一个专属的工作线程
- 创建出来的线程名字类似:
nioEventLoopGroup-2-1、nioEventLoopGroup-2-2等
为什么不用线程池?
因为每个 NioEventLoop 只需要一个固定的线程,这个线程会一直运行,不需要复用。
步骤3:创建线程选择器(EventExecutorChooser)
// 在 MultithreadEventExecutorGroup 中
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
}
DefaultEventExecutorChooserFactory 的实现:
public final class DefaultEventExecutorChooserFactory implements EventExecutorChooserFactory {
@Override
public EventExecutorChooser newChooser(EventExecutor[] executors) {
// 判断线程数是否是 2 的幂次方
if (isPowerOfTwo(executors.length)) {
// 如果是 2 的幂次方,使用位运算优化
return new PowerOfTwoEventExecutorChooser(executors);
} else {
// 否则使用普通的取模运算
return new GenericEventExecutorChooser(executors);
}
}
private static boolean isPowerOfTwo(int val) {
return (val & -val) == val;
}
}
两种 Chooser 的实现:
// 1. 位运算优化版本(当线程数是 2 的幂次方时使用)
private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
private final AtomicInteger idx = new AtomicInteger();
private final EventExecutor[] executors;
@Override
public EventExecutor next() {
// 使用位运算代替取模,性能更好
return executors[idx.getAndIncrement() & executors.length - 1];
}
}
// 2. 普通取模版本
private static final class GenericEventExecutorChooser implements EventExecutorChooser {
private final AtomicInteger idx = new AtomicInteger();
private final EventExecutor[] executors;
@Override
public EventExecutor next() {
// 使用取模运算
return executors[Math.abs(idx.getAndIncrement() % executors.length)];
}
}
通俗解释:
- Chooser 的作用是:从线程池中选择一个线程来处理新连接
- 选择策略是轮询(Round-Robin):0 → 1 → 2 → 3 → 0 → 1 → …
- 如果线程数是 2 的幂次方(2、4、8、16…),使用位运算优化,速度更快
- 否则使用普通的取模运算
举个例子:
假设你创建了 4 个线程(2 的幂次方):
第 1 个连接 → idx=0 → 0 & 3 = 0 → 选择线程 0
第 2 个连接 → idx=1 → 1 & 3 = 1 → 选择线程 1
第 3 个连接 → idx=2 → 2 & 3 = 2 → 选择线程 2
第 4 个连接 → idx=3 → 3 & 3 = 3 → 选择线程 3
第 5 个连接 → idx=4 → 4 & 3 = 0 → 选择线程 0(循环)
步骤4:创建 Selector 提供者(SelectorProvider)
public NioEventLoopGroup(int nThreads, Executor executor) {
// 使用系统默认的 SelectorProvider
this(nThreads, executor, SelectorProvider.provider());
}
在 NioEventLoop 中的使用:
// 每个 NioEventLoop 创建时,会使用 SelectorProvider 创建自己的 Selector
Selector selector = selectorProvider.openSelector();
通俗解释:
SelectorProvider是 Java NIO 提供的类,用于创建 Selector- 每个 NioEventLoop 都有自己独立的 Selector
- Selector 是 NIO 的核心,用于监听多个 Channel 的 IO 事件
- 通常我们使用系统默认的 SelectorProvider 就可以了
步骤5:创建选择策略(SelectStrategy)
public NioEventLoopGroup(int nThreads, Executor executor,
final SelectorProvider selectorProvider) {
this(nThreads, executor, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);
}
DefaultSelectStrategyFactory 的实现:
final class DefaultSelectStrategyFactory implements SelectStrategyFactory {
static final SelectStrategyFactory INSTANCE = new DefaultSelectStrategyFactory();
@Override
public SelectStrategy newSelectStrategy() {
return DefaultSelectStrategy.INSTANCE;
}
}
DefaultSelectStrategy 的实现:
final class DefaultSelectStrategy implements SelectStrategy {
static final SelectStrategy INSTANCE = new DefaultSelectStrategy();
@Override
public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) throws Exception {
// 如果有任务,执行非阻塞的 selectNow(),立即返回
// 如果没有任务,返回 SELECT 标记,后续会执行阻塞的 select()
return hasTasks ? selectSupplier.get() : SelectStrategy.SELECT;
}
}
通俗解释:
- SelectStrategy 决定了 NioEventLoop 的工作模式
- 如果任务队列有任务:执行
selectNow()(非阻塞,立即返回) - 如果任务队列没有任务:执行
select()(阻塞等待 IO 事件) - 这样可以在处理 IO 事件和执行任务之间取得平衡
为什么要这样设计?
- 如果一直阻塞在
select()上,任务队列的任务就得不到及时处理 - 如果一直执行
selectNow(),会浪费 CPU 资源 - 所以根据是否有任务来动态选择,既能及时处理任务,又不会浪费 CPU
步骤6:创建拒绝策略(RejectedExecutionHandler)
public NioEventLoopGroup(int nThreads, Executor executor,
final SelectorProvider selectorProvider,
final SelectStrategyFactory selectStrategyFactory) {
super(nThreads, executor, selectorProvider, selectStrategyFactory,
RejectedExecutionHandlers.reject()); // 使用默认的拒绝策略
}
RejectedExecutionHandlers 的实现:
public final class RejectedExecutionHandlers {
// 直接拒绝,抛出异常
public static RejectedExecutionHandler reject() {
return REJECT;
}
private static final RejectedExecutionHandler REJECT = new RejectedExecutionHandler() {
@Override
public void rejected(Runnable task, SingleThreadEventExecutor executor) {
throw new RejectedExecutionException("Task " + task.toString() +
" rejected from " + executor.toString());
}
};
}
通俗解释:
- 每个 NioEventLoop 都有一个任务队列(默认容量是无限的)
- 如果队列满了(或者 EventLoop 已经关闭),新任务会被拒绝
- 默认的拒绝策略是:直接抛出
RejectedExecutionException异常 - 这和
ThreadPoolExecutor的拒绝策略类似
步骤7:创建 children 数组并初始化(核心)
这是整个初始化过程中最核心的部分!所有参数设置完成后,会进入这个方法:
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 ==========
if (executor == null) {
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
// ========== 3. 创建 children 数组(核心数据结构)==========
children = new EventExecutor[nThreads];
// ========== 4. 循环创建每一个 NioEventLoop 实例 ==========
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
try {
// 调用 newChild() 方法创建 NioEventLoop
// 这个方法在 NioEventLoopGroup 中被覆写
children[i] = newChild(executor, args);
success = true;
} catch (Exception e) {
throw new IllegalStateException("failed to create a child event loop", e);
} finally {
// ========== 5. 如果创建失败,需要清理已经创建的 NioEventLoop ==========
if (!success) {
// 关闭已经创建成功的 EventLoop
for (int j = 0; j < i; j ++) {
children[j].shutdownGracefully();
}
// 等待它们完全终止
for (int j = 0; j < i; j ++) {
EventExecutor e = children[j];
try {
while (!e.isTerminated()) {
e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
}
} catch (InterruptedException interrupted) {
Thread.currentThread().interrupt();
break;
}
}
}
}
}
// ========== 6. 创建 Chooser,用于从 children 数组中选择 EventLoop ==========
chooser = chooserFactory.newChooser(children);
// ========== 7. 设置终止监听器 ==========
final FutureListener<Object> terminationListener = new FutureListener<Object>() {
@Override
public void operationComplete(Future<Object> future) throws Exception {
// 当所有 EventLoop 都终止时,设置 terminationFuture 为成功
if (terminatedChildren.incrementAndGet() == children.length) {
terminationFuture.setSuccess(null);
}
}
};
// 给每个 EventLoop 添加终止监听器
for (EventExecutor e: children) {
e.terminationFuture().addListener(terminationListener);
}
// ========== 8. 创建只读的 children 集合 ==========
Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
Collections.addAll(childrenSet, children);
readonlyChildren = Collections.unmodifiableSet(childrenSet);
}
让我们逐步解析这个方法:
3.1 参数校验
if (nThreads <= 0) {
throw new IllegalArgumentException(
String.format("nThreads: %d (expected: > 0)", nThreads));
}
很简单,线程数必须大于 0。
3.2 创建 children 数组
children = new EventExecutor[nThreads];
这是 NioEventLoopGroup 的核心数据结构,用于存储所有的 NioEventLoop 实例。
比如你创建了 4 个线程:
children[0] = NioEventLoop-0
children[1] = NioEventLoop-1
children[2] = NioEventLoop-2
children[3] = NioEventLoop-3
3.3 循环创建 NioEventLoop
for (int i = 0; i < nThreads; i ++) {
children[i] = newChild(executor, args);
}
newChild() 方法在 NioEventLoopGroup 中的实现:
@Override
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
// args[0] = SelectorProvider
// args[1] = SelectStrategyFactory
// args[2] = RejectedExecutionHandler
return new NioEventLoop(this, executor, (SelectorProvider) args[0],
((SelectStrategyFactory) args[1]).newSelectStrategy(),
(RejectedExecutionHandler) args[2]);
}
通俗解释:
- 循环
nThreads次,每次创建一个 NioEventLoop - 每个 NioEventLoop 都会被赋予:
- 父 Group(this)
- 线程创建器(executor)
- Selector 提供者(selectorProvider)
- 选择策略(selectStrategy)
- 拒绝策略(rejectedExecutionHandler)
3.4 异常处理机制
finally {
if (!success) {
// 关闭已经创建成功的 EventLoop
for (int j = 0; j < i; j ++) {
children[j].shutdownGracefully();
}
// 等待它们完全终止
for (int j = 0; j < i; j ++) {
EventExecutor e = children[j];
try {
while (!e.isTerminated()) {
e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
}
} catch (InterruptedException interrupted) {
Thread.currentThread().interrupt();
break;
}
}
}
}
通俗解释:
- 如果创建 NioEventLoop 的过程中出现异常,需要清理已经创建的 EventLoop
- 比如创建了 3 个,第 4 个失败了,那么需要关闭前 3 个
- 这是一种资源清理机制,防止资源泄漏
3.5 创建 Chooser
chooser = chooserFactory.newChooser(children);
创建线程选择器,用于从 children 数组中选择 NioEventLoop。
3.6 设置终止监听器
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);
}
通俗解释:
- 给每个 NioEventLoop 添加一个监听器
- 当一个 NioEventLoop 终止时,
terminatedChildren计数器加 1 - 当所有 NioEventLoop 都终止时,设置
terminationFuture为成功 - 这样外部就可以通过
terminationFuture来等待整个 Group 关闭
3.7 创建只读集合
Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
Collections.addAll(childrenSet, children);
readonlyChildren = Collections.unmodifiableSet(childrenSet);
创建一个只读的 Set,防止外部修改 children 数组。
四、NioEventLoopGroup 的核心方法
初始化完成后,NioEventLoopGroup 就可以使用了。我们来看看它的核心方法:
4.1 next() - 选择一个 EventLoop
@Override
public EventLoop next() {
return (EventLoop) chooser.next();
}
通俗解释:
- 使用 Chooser 从 children 数组中选择一个 NioEventLoop
- 选择策略是轮询(Round-Robin)
- 每次调用
next()都会返回下一个 EventLoop
使用场景:
// 当有新连接时,选择一个 EventLoop 来处理
EventLoop eventLoop = workerGroup.next();
eventLoop.register(channel);
4.2 register() - 注册 Channel
@Override
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
通俗解释:
- 选择一个 EventLoop,然后将 Channel 注册到这个 EventLoop 上
- 一旦注册,这个 Channel 就会一直由这个 EventLoop 处理
4.3 shutdownGracefully() - 优雅关闭
@Override
public Future<?> shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit) {
// 关闭所有的 EventLoop
for (EventLoop l: children) {
l.shutdownGracefully(quietPeriod, timeout, unit);
}
return terminationFuture();
}
通俗解释:
- 关闭所有的 NioEventLoop
- 每个 EventLoop 会先处理完队列中的任务,然后再关闭
- 返回一个 Future,可以用来等待所有 EventLoop 关闭完成
五、完整的初始化流程图
让我们用一张图来总结整个初始化过程:
new NioEventLoopGroup(4)
|
v
步骤1: 确定线程数 = 4
|
v
步骤2: 创建 ThreadPerTaskExecutor
|
v
步骤3: 创建 PowerOfTwoEventExecutorChooser (因为 4 是 2 的幂次方)
|
v
步骤4: 创建 SelectorProvider
|
v
步骤5: 创建 DefaultSelectStrategy
|
v
步骤6: 创建 RejectedExecutionHandler
|
v
步骤7: 创建 children 数组并初始化
|
+---> 创建 NioEventLoop-0
| |
| +---> 创建 Selector-0
| +---> 创建 TaskQueue-0
|
+---> 创建 NioEventLoop-1
| |
| +---> 创建 Selector-1
| +---> 创建 TaskQueue-1
|
+---> 创建 NioEventLoop-2
| |
| +---> 创建 Selector-2
| +---> 创建 TaskQueue-2
|
+---> 创建 NioEventLoop-3
|
+---> 创建 Selector-3
+---> 创建 TaskQueue-3
六、总结
通过这篇文章,我们深入学习了 NioEventLoopGroup 的源码实现:
- NioEventLoopGroup 是什么:Netty 的线程池实现,专为 NIO 设计
- 核心组件:children 数组、Chooser、Executor、SelectorProvider 等
- 初始化过程:7 个步骤,从参数设置到创建 NioEventLoop
- 核心方法:next()、register()、shutdownGracefully()
- 设计思想:固定线程数、线程绑定、轮询选择
希望这篇文章能帮助你理解 NioEventLoopGroup 的源码实现。如果你想深入学习,建议继续阅读 NioEventLoop 的源码,了解单个线程是如何工作的。
推荐阅读:
- NioEventLoop 运行机制详解
- Netty 服务端启动流程详解
- Netty Channel 注册流程详解
如果这篇文章对你有帮助,欢迎点赞、收藏、分享!
本文详细介绍了 NioEventLoopGroup 的源码实现,更多 Netty 源码解析请查看
NioEventLoopGroup深度解析.md文档。
1863

被折叠的 条评论
为什么被折叠?



