相关文章链接
processSelectedKeys() vs runAllTasks()
NioServerSocketChannel-Unsafe初始化详解
NioEventLoopGroup 深度解析
本文详细介绍 Netty 中的线程池 NioEventLoopGroup 的设计原理、核心参数、创建流程以及与 NioEventLoop 的关系。
一、NioEventLoopGroup 是什么?
1.1 本质定义
NioEventLoopGroup 是 Netty 中的线程池实现,但它不是传统意义上的 ThreadPoolExecutor,而是专门为事件驱动的 NIO 模型设计的线程池。
从类的命名可以看出:
- Group:表示这是一个组,一个集合
- EventLoop:表示组中的每个元素都是事件循环
- Nio:表示基于 Java NIO 实现
1.2 核心概念对应关系
NioEventLoopGroup(线程池)
├── NioEventLoop(线程1)
├── NioEventLoop(线程2)
├── NioEventLoop(线程3)
└── ...
重要理解:
NioEventLoopGroup= 线程池(管理者)NioEventLoop= 线程池中的单个"线程"(工作者)- 每个
NioEventLoop内部有一个真正的Thread实例
1.3 在 Netty 中的使用场景
// 服务端典型用法
EventLoopGroup bossGroup = new NioEventLoopGroup(1); // 负责接收连接
EventLoopGroup workerGroup = new NioEventLoopGroup(); // 负责处理 IO
// 客户端典型用法
EventLoopGroup group = new NioEventLoopGroup(); // 负责连接和 IO
bossGroup 和 workerGroup 的职责划分:
- bossGroup:专门负责监听
OP_ACCEPT事件,接收客户端连接 - workerGroup:负责处理已建立连接的
OP_READ、OP_WRITE等 IO 事件
二、NioEventLoopGroup 的继承体系
2.1 完整的继承链
NioEventLoopGroup
↓ extends
MultithreadEventLoopGroup
↓ extends
MultithreadEventExecutorGroup
↓ extends
AbstractEventExecutorGroup
↓ implements
EventLoopGroup
↓ extends
EventExecutorGroup
↓ extends
ScheduledExecutorService, Iterable<EventExecutor>
2.2 关键接口和抽象类的作用
EventExecutorGroup(顶层接口)
- 继承自 JDK 的
ScheduledExecutorService - 提供了线程池的基本能力:提交任务、调度任务、关闭线程池等
- 增加了
next()方法:从线程池中选择一个 EventExecutor
EventLoopGroup(事件循环组接口)
- 继承自
EventExecutorGroup - 增加了
register(Channel)方法:将 Channel 注册到某个 EventLoop 上 - 这是 Netty 特有的,将 Channel 和线程池关联起来
MultithreadEventExecutorGroup(多线程执行器组)
- 这是核心实现类,实现了线程池的主要逻辑
- 维护了
children数组,存储所有的 EventExecutor(即 NioEventLoop) - 实现了线程选择逻辑(通过 Chooser)
MultithreadEventLoopGroup(多线程事件循环组)
- 继承自
MultithreadEventExecutorGroup - 设置了默认线程数:
CPU 核心数 * 2 - 实现了
register(Channel)方法
NioEventLoopGroup(NIO 事件循环组)
- 最终的实现类
- 覆写了
newChild()方法,创建NioEventLoop实例 - 设置了 NIO 相关的默认参数(SelectorProvider、SelectStrategy 等)
三、NioEventLoopGroup 的构造过程详解
3.1 构造方法的重载链
// 1. 无参构造(最常用)
public NioEventLoopGroup() {
this(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());
}
3.2 构造过程中的参数设置
第一步:设置线程数(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));
}
关键点:
- 默认线程数 =
CPU 核心数 * 2 - 可以通过系统属性
io.netty.eventLoopThreads自定义 - 最小值为 1
为什么是 CPU 核心数 * 2?
- NIO 是 IO 密集型操作,线程经常会阻塞在
select()上 - 适当增加线程数可以提高并发处理能力
- 但也不能太多,避免线程切换开销
第二步:设置 Executor
// 在 MultithreadEventExecutorGroup 中
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
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();
}
}
重要理解:
ThreadPerTaskExecutor不是传统的线程池,它每次都创建新线程- 这个 Executor 不是给 NioEventLoopGroup 用的
- 而是给每个
NioEventLoop用的,用于创建 NioEventLoop 内部的那个唯一线程 - 每个 NioEventLoop 只会调用一次
executor.execute(),创建自己的工作线程
为什么这样设计?
- NioEventLoop 需要一个长期运行的线程来执行事件循环
- 这个线程不能被线程池回收,必须是专属的
- 使用 ThreadPerTaskExecutor 可以灵活控制线程的创建(线程名、优先级等)
第三步:设置 EventExecutorChooserFactory
// 在 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)) {
return new PowerOfTwoEventExecutorChooser(executors);
} else {
return new GenericEventExecutorChooser(executors);
}
}
private static boolean isPowerOfTwo(int val) {
return (val & -val) == val;
}
}
两种选择策略:
- PowerOfTwoEventExecutorChooser(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];
}
}
- GenericEventExecutorChooser(通用选择器)
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)];
}
}
为什么要区分两种策略?
- 当线程数是 2 的幂次方时,
x % n等价于x & (n-1) - 位运算比取模运算快得多
- 这是一个性能优化的细节
选择策略的作用:
- 当有新的 Channel 需要注册时,通过
chooser.next()选择一个 NioEventLoop - 采用轮询(Round-Robin)的方式,保证负载均衡
- 例如:线程池有 4 个线程,依次选择 0 -> 1 -> 2 -> 3 -> 0 -> 1 …
第四步:设置 SelectorProvider
public NioEventLoopGroup(int nThreads, Executor executor) {
this(nThreads, executor, SelectorProvider.provider());
}
SelectorProvider 的作用:
- 这是 JDK NIO 提供的类,用于创建 Selector、ServerSocketChannel、SocketChannel 等
SelectorProvider.provider()返回平台默认的实现- 在 Windows 上是
WindowsSelectorProvider - 在 Linux 上是
EPollSelectorProvider(如果支持 epoll)
为什么需要 SelectorProvider?
- 每个 NioEventLoop 都需要一个 Selector 实例
- Selector 不能直接 new,必须通过 SelectorProvider 创建
- 这样可以保证使用平台最优的实现
// 在 NioEventLoop 的构造方法中会使用
Selector selector = selectorProvider.openSelector();
第五步:设置 SelectStrategyFactory
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 在事件循环中是执行阻塞的
select()还是非阻塞的selectNow() - 如果任务队列中有任务,使用
selectNow()快速返回,优先处理任务 - 如果任务队列为空,使用
select()阻塞等待 IO 事件
这是一个重要的性能优化:
- 避免在有任务等待时,线程阻塞在 select() 上
- 保证任务能够及时被执行
第六步:设置 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();
}
};
}
拒绝策略的触发时机:
- 每个 NioEventLoop 内部有一个任务队列(taskQueue),默认容量是 16
- 当任务队列满了,再提交任务时,会触发拒绝策略
- 默认策略是直接抛出
RejectedExecutionException
为什么任务队列容量这么小?
- NioEventLoop 的设计理念是快速处理 IO 事件
- 不应该有大量任务堆积在队列中
- 如果队列满了,说明系统负载过高,应该及时发现问题
3.3 MultithreadEventExecutorGroup 的核心构造逻辑
这是整个构造过程中最核心的部分,所有参数设置完成后,会进入这个方法:
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,创建默认的 ThreadPerTaskExecutor
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 {
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);
}
上述代码的关键点详解:
children 数组(最核心)
children = new EventExecutor[nThreads];
- 这是 NioEventLoopGroup 最核心的数据结构
- 数组长度就是线程池的大小
- 每个元素都是一个 NioEventLoop 实例
- 后续所有的操作都是基于这个数组
newChild() 方法
// 在 NioEventLoopGroup 中覆写
@Override
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
return new NioEventLoop(this, executor, (SelectorProvider) args[0],
((SelectStrategyFactory) args[1]).newSelectStrategy(),
(RejectedExecutionHandler) args[2]);
}
- 这个方法负责创建单个 NioEventLoop 实例
- 传入的参数包括:
this:NioEventLoopGroup 自己,作为 parentexecutor:ThreadPerTaskExecutor,用于创建线程SelectorProvider:用于创建 SelectorSelectStrategy:选择策略RejectedExecutionHandler:拒绝策略
异常处理机制
if (!success) {
// 关闭已经创建成功的 EventLoop
for (int j = 0; j < i; j ++) {
children[j].shutdownGracefully();
}
// 等待它们完全终止
...
}
- 如果某个 NioEventLoop 创建失败,需要清理已经创建的
- 这是一种"要么全部成功,要么全部失败"的策略
- 保证线程池的状态一致性
终止监听器
final FutureListener<Object> terminationListener = new FutureListener<Object>() {
@Override
public void operationComplete(Future<Object> future) throws Exception {
if (terminatedChildren.incrementAndGet() == children.length) {
terminationFuture.setSuccess(null);
}
}
};
- 当所有的 NioEventLoop 都终止后,整个 NioEventLoopGroup 才算终止
- 使用计数器
terminatedChildren来跟踪已终止的 EventLoop 数量 - 当计数器等于 children.length 时,设置 terminationFuture 为成功
四、NioEventLoop 的创建过程
4.1 NioEventLoop 的构造方法
NioEventLoop(NioEventLoopGroup parent, Executor executor,
SelectorProvider selectorProvider,
SelectStrategy strategy,
RejectedExecutionHandler rejectedExecutionHandler) {
// 调用父类构造方法
super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler);
if (selectorProvider == null) {
throw new NullPointerException("selectorProvider");
}
if (strategy == null) {
throw new NullPointerException("selectStrategy");
}
// 保存 SelectorProvider
provider = selectorProvider;
// 创建 Selector(NIO 的核心组件)
final SelectorTuple selectorTuple = openSelector();
selector = selectorTuple.selector;
unwrappedSelector = selectorTuple.unwrappedSelector;
// 保存选择策略
selectStrategy = strategy;
}
4.2 NioEventLoop 的核心属性
public final class NioEventLoop extends SingleThreadEventLoop {
// ========== NIO 相关 ==========
// Selector,NIO 的核心,用于监听 IO 事件
private Selector selector;
private Selector unwrappedSelector;
// 优化后的 SelectedKeys 集合
private SelectedSelectionKeySet selectedKeys;
// SelectorProvider,用于创建 Selector
private final SelectorProvider provider;
// 唤醒标记,用于唤醒阻塞在 select() 上的线程
private final AtomicBoolean wakenUp = new AtomicBoolean();
// 选择策略
private final SelectStrategy selectStrategy;
// ========== 性能调优相关 ==========
// IO 操作和非 IO 操作的时间比例,默认 50
// 表示 IO 操作和处理任务的时间比例是 1:1
private volatile int ioRatio = 50;
// 取消的 Key 数量
private int cancelledKeys;
// 是否需要再次 select
private boolean needsToSelectAgain;
}
4.3 SingleThreadEventLoop 的构造方法
protected SingleThreadEventLoop(EventLoopGroup parent, Executor executor,
boolean addTaskWakesUp, int maxPendingTasks,
RejectedExecutionHandler rejectedExecutionHandler) {
// 调用父类构造方法
super(parent, executor, addTaskWakesUp, maxPendingTasks, rejectedExecutionHandler);
// tailTasks 是一个特殊的任务队列,在每次事件循环的最后执行
// 通常用于一些清理工作,我们可以暂时忽略它
tailTasks = newTaskQueue(maxPendingTasks);
}
4.4 SingleThreadEventExecutor 的构造方法(最核心)
protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor,
boolean addTaskWakesUp, int maxPendingTasks,
RejectedExecutionHandler rejectedHandler) {
super(parent);
// addTaskWakesUp:添加任务时是否需要唤醒线程
// 对于 NioEventLoop,这个值是 false
this.addTaskWakesUp = addTaskWakesUp;
// 最大待处理任务数,默认是 16
this.maxPendingTasks = Math.max(16, maxPendingTasks);
// 保存 executor,用于创建线程
this.executor = ObjectUtil.checkNotNull(executor, "executor");
// 创建任务队列,这是一个非常重要的队列
taskQueue = newTaskQueue(this.maxPendingTasks);
// 拒绝策略
rejectedExecutionHandler = ObjectUtil.checkNotNull(rejectedHandler, "rejectedHandler");
}
关键属性详解:
taskQueue(任务队列)
taskQueue = newTaskQueue(this.maxPendingTasks);
protected Queue<Runnable> newTaskQueue(int maxPendingTasks) {
// 默认使用 MpscQueue(Multiple Producer Single Consumer Queue)
// 多生产者单消费者队列,针对 Netty 的使用场景优化
return new MpscUnboundedArrayQueue<Runnable>(maxPendingTasks);
}
- 这是 NioEventLoop 中最重要的队列之一
- 用于存储提交给 NioEventLoop 的任务
- 默认初始容量是 16,但可以动态扩容(Unbounded)
- 使用 MPSC 队列,支持多个线程同时提交任务,但只有一个线程消费
taskQueue 的作用:
- 存储用户提交的任务(通过
execute(Runnable)方法) - 存储 Netty 内部的任务(如 register、bind、connect 等)
- 在事件循环中,会定期从这个队列中取出任务执行
executor(线程创建器)
this.executor = ObjectUtil.checkNotNull(executor, "executor");
- 这就是前面创建的
ThreadPerTaskExecutor - 它的作用是创建 NioEventLoop 的工作线程
- 注意:此时线程还没有创建
- 线程会在第一个任务提交时才创建(延迟创建)
thread(工作线程)
// 在 SingleThreadEventExecutor 中
private volatile Thread thread;
- 这是 NioEventLoop 真正的工作线程
- 初始值为 null,在第一次执行任务时才会创建
- 一旦创建,就会一直运行,直到 NioEventLoop 关闭
- 这个线程会执行事件循环(run 方法)
4.5 openSelector() 方法详解
private SelectorTuple openSelector() {
final Selector unwrappedSelector;
try {
// 通过 SelectorProvider 创建 Selector
unwrappedSelector = provider.openSelector();
} catch (IOException e) {
throw new ChannelException("failed to open a new selector", e);
}
// 如果禁用了 Selector 优化,直接返回
if (DISABLE_KEY_SET_OPTIMIZATION) {
return new SelectorTuple(unwrappedSelector);
}
// 下面是 Netty 对 Selector 的优化
// 将 Selector 内部的 selectedKeys 替换为 Netty 优化的实现
// 这部分是性能优化,可以提高 select 后遍历 keys 的效率
Object maybeSelectorImplClass = AccessController.doPrivileged(...);
// 通过反射替换 selectedKeys 和 publicSelectedKeys
final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
// ... 反射代码省略 ...
selectedKeys = selectedKeySet;
return new SelectorTuple(unwrappedSelector,
new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet));
}
Selector 优化的原理:
- JDK 原生的 Selector 使用 HashSet 存储 selectedKeys
- Netty 使用数组实现的 SelectedSelectionKeySet,遍历效率更高
- 通过反射将 Selector 内部的 selectedKeys 替换为 Netty 的实现
五、NioEventLoopGroup 的核心方法
5.1 register(Channel) 方法
这是 NioEventLoopGroup 最重要的方法,用于将 Channel 注册到某个 NioEventLoop 上。
// MultithreadEventLoopGroup
@Override
public ChannelFuture register(Channel channel) {
// 通过 next() 方法选择一个 EventLoop
return next().register(channel);
}
next() 方法的实现:
// MultithreadEventExecutorGroup
@Override
public EventExecutor next() {
// 使用 chooser 选择一个 EventLoop
return chooser.next();
}
完整的调用链:
NioEventLoopGroup.register(channel)
↓
next() 选择一个 NioEventLoop
↓
NioEventLoop.register(channel)
↓
将 channel 注册到这个 NioEventLoop 的 Selector 上
关键点:
- 一个 Channel 只会注册到一个 NioEventLoop 上
- 一旦注册,这个 Channel 的所有 IO 事件都由这个 NioEventLoop 处理
- 这保证了线程安全,避免了多线程竞争
5.2 execute(Runnable) 方法
虽然 NioEventLoopGroup 实现了 Executor 接口,但通常我们不会直接调用它的 execute 方法。
// MultithreadEventExecutorGroup
@Override
public void execute(Runnable command) {
// 选择一个 EventLoop 来执行任务
next().execute(command);
}
更常见的用法是直接在 NioEventLoop 上执行任务:
channel.eventLoop().execute(() -> {
// 这个任务会在 channel 所属的 NioEventLoop 中执行
System.out.println("Task executed in " + Thread.currentThread().getName());
});
5.3 shutdownGracefully() 方法
优雅关闭线程池的方法。
// MultithreadEventExecutorGroup
@Override
public Future<?> shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit) {
// 关闭所有的 EventLoop
for (EventExecutor l: children) {
l.shutdownGracefully(quietPeriod, timeout, unit);
}
return terminationFuture();
}
优雅关闭的流程:
- 停止接收新任务
- 等待已提交的任务执行完成
- 在 quietPeriod 时间内,如果没有新任务,则关闭
- 如果超过 timeout 时间,强制关闭
六、NioEventLoopGroup 的重要特性总结
6.1 线程模型
NioEventLoopGroup(线程池)
├── NioEventLoop-1(线程1)
│ ├── Thread-1(真正的线程)
│ ├── Selector-1(NIO Selector)
│ ├── taskQueue-1(任务队列)
│ └── 管理多个 Channel
│
├── NioEventLoop-2(线程2)
│ ├── Thread-2
│ ├── Selector-2
│ ├── taskQueue-2
│ └── 管理多个 Channel
│
└── ...
关键特性:
- 一个 NioEventLoop 对应一个线程
- 一个 NioEventLoop 有一个 Selector
- 一个 NioEventLoop 可以管理多个 Channel
- 一个 Channel 只能注册到一个 NioEventLoop
6.2 线程安全性
NioEventLoop 的线程安全保证:
- 每个 Channel 的所有操作都在同一个 NioEventLoop 中执行
- 避免了多线程竞争,不需要加锁
- 这是 Netty 高性能的关键之一
判断是否在 EventLoop 线程中:
if (channel.eventLoop().inEventLoop()) {
// 当前线程就是 EventLoop 线程,直接执行
doSomething();
} else {
// 当前线程不是 EventLoop 线程,提交任务
channel.eventLoop().execute(() -> doSomething());
}
6.3 任务执行模型
NioEventLoop 需要处理两类任务:
1. IO 任务
- 监听 Selector 上的 IO 事件(OP_ACCEPT、OP_READ、OP_WRITE 等)
- 处理这些 IO 事件(读取数据、写入数据等)
2. 非 IO 任务
- 用户提交的任务(通过 execute 方法)
- Netty 内部的任务(register、bind、connect 等)
ioRatio 参数:
private volatile int ioRatio = 50;
- 控制 IO 任务和非 IO 任务的时间分配比例
- 默认值 50,表示各占 50%
- 如果 IO 操作耗时 100ms,那么处理非 IO 任务最多也是 100ms
为什么需要 ioRatio?
- 防止某一类任务占用过多时间
- 保证 IO 事件能够及时处理
- 也保证用户任务能够及时执行
6.4 延迟创建线程
重要特性:NioEventLoop 的线程不是在构造时创建的,而是在第一个任务提交时创建。
// 创建 NioEventLoopGroup
EventLoopGroup group = new NioEventLoopGroup();
// 此时线程还没有创建
// 第一次提交任务时,才会创建线程
group.execute(() -> {
System.out.println("First task");
});
// 此时线程已经创建并启动
为什么要延迟创建?
- 避免创建不必要的线程
- 如果某个 NioEventLoop 一直没有任务,就不会创建线程
- 节省系统资源
线程创建的时机:
- 第一次调用
execute(Runnable)时 - 第一次
register(Channel)时 - 这些操作都会触发
startThread()方法
八、核心参数总结表
| 参数 | 默认值 | 作用 | 调优建议 |
|---|---|---|---|
| nThreads | CPU 核心数 * 2 | 线程池大小 | bossGroup 设为 1,workerGroup 根据压测调整 |
| Executor | ThreadPerTaskExecutor | 创建线程 | 通常不需要自定义 |
| ChooserFactory | DefaultEventExecutorChooserFactory | 选择 EventLoop | 不需要自定义 |
| SelectorProvider | 平台默认实现 | 创建 Selector | 不需要自定义 |
| SelectStrategyFactory | DefaultSelectStrategyFactory | 选择策略 | 不需要自定义 |
| RejectedExecutionHandler | 抛出异常 | 任务拒绝策略 | 可以自定义为记录日志 |
| ioRatio | 50 | IO 和非 IO 任务时间比 | 根据业务调整,IO 密集型可以调高 |
| maxPendingTasks | 16 | 任务队列初始容量 | 任务多时可以调大 |
十、深入理解:为什么这样设计?
10.1 为什么 NioEventLoop 是单线程的?
传统多线程模型的问题:
// 假设多个线程处理同一个 Channel
Thread-1: 读取数据 -> 处理 -> 写入响应
Thread-2: 读取数据 -> 处理 -> 写入响应
// 问题:需要加锁,性能下降,而且可能乱序
Netty 的单线程模型:
// 一个 Channel 绑定到一个 EventLoop
EventLoop-1: 读取数据 -> 处理 -> 写入响应
// 优势:无需加锁,保证顺序,性能更高
单线程模型的优势:
- 无锁化:同一个 Channel 的操作都在一个线程中,不需要加锁
- 顺序性:保证事件的处理顺序
- 简化编程:不需要考虑线程安全问题
- 高性能:避免了锁竞争和上下文切换
10.2 为什么需要 EventLoopGroup?
单个 EventLoop 的局限:
- 一个线程只能处理有限的 Channel
- 无法充分利用多核 CPU
EventLoopGroup 的优势:
- 多个 EventLoop 并行工作
- 每个 EventLoop 处理一部分 Channel
- 充分利用多核 CPU
负载均衡:
10000 个 Channel
↓ 通过 Chooser 分配
EventLoop-1: 2500 个 Channel
EventLoop-2: 2500 个 Channel
EventLoop-3: 2500 个 Channel
EventLoop-4: 2500 个 Channel
10.3 为什么使用 Reactor 模式?
Reactor 模式的核心思想:
- 使用 Selector 监听多个 Channel 的事件
- 事件到达时,分发给对应的 Handler 处理
- 一个线程可以处理多个连接
Netty 的实现:
NioEventLoop (Reactor)
↓
Selector.select() 监听事件
↓
有事件到达
↓
processSelectedKeys() 处理 IO 事件
↓
runAllTasks() 处理非 IO 任务
↓
循环往复
优势:
- 一个线程可以管理成千上万个连接
- 避免了传统 BIO 的一个连接一个线程的问题
- 大大提高了系统的并发能力
十一、源码追踪:从创建到运行的完整流程
11.1 创建流程图
new NioEventLoopGroup()
↓
设置默认参数(nThreads、executor、chooserFactory 等)
↓
MultithreadEventExecutorGroup 构造方法
↓
创建 children 数组
↓
循环调用 newChild() 创建 NioEventLoop
↓
每个 NioEventLoop 创建时:
- 创建 taskQueue
- 创建 Selector
- 保存 executor(用于后续创建线程)
- 此时线程还未创建!
↓
创建 Chooser(用于选择 EventLoop)
↓
设置终止监听器
↓
NioEventLoopGroup 创建完成
11.2 第一次使用流程图
channel.register(eventLoopGroup)
↓
eventLoopGroup.next() 选择一个 NioEventLoop
↓
nioEventLoop.register(channel)
↓
nioEventLoop.execute(register 任务)
↓
判断线程是否已创建?
├─ 否 → startThread()
│ ↓
│ executor.execute(new Runnable() {
│ thread = Thread.currentThread();
│ NioEventLoop.this.run(); // 启动事件循环
│ })
│ ↓
│ 线程创建并启动
│ ↓
└─ 是 → 直接添加任务到 taskQueue
↓
任务在事件循环中被执行
11.3 事件循环的运行流程
NioEventLoop.run() {
for (;;) { // 无限循环
// 1. 选择策略:是否有任务?
if (hasTasks()) {
selectNow(); // 非阻塞 select
} else {
select(); // 阻塞 select
}
// 2. 处理 IO 事件
processSelectedKeys();
// 3. 处理任务队列中的任务
runAllTasks();
// 4. 检查是否需要关闭
if (isShuttingDown()) {
closeAll();
if (confirmShutdown()) {
return; // 退出循环
}
}
}
}
十二、关键代码片段详解
12.1 线程的延迟创建
// SingleThreadEventExecutor.execute()
@Override
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
// 判断当前线程是否是 EventLoop 线程
boolean inEventLoop = inEventLoop();
// 添加任务到队列
addTask(task);
if (!inEventLoop) {
// 如果不是 EventLoop 线程,启动线程
startThread();
// 如果已经关闭,移除任务并拒绝
if (isShutdown() && removeTask(task)) {
reject();
}
}
// 如果需要,唤醒线程
if (!addTaskWakesUp && wakesUpForTask(task)) {
wakeup(inEventLoop);
}
}
startThread() 方法:
private void startThread() {
// 使用 CAS 保证只启动一次
if (state == ST_NOT_STARTED) {
if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
try {
doStartThread();
} catch (Throwable cause) {
STATE_UPDATER.set(this, ST_NOT_STARTED);
PlatformDependent.throwException(cause);
}
}
}
}
doStartThread() 方法:
private void doStartThread() {
assert thread == null;
// 使用 ThreadPerTaskExecutor 创建线程
executor.execute(new Runnable() {
@Override
public void run() {
// 保存线程引用
thread = Thread.currentThread();
if (interrupted) {
thread.interrupt();
}
boolean success = false;
updateLastExecutionTime();
try {
// 执行事件循环
SingleThreadEventExecutor.this.run();
success = true;
} catch (Throwable t) {
logger.warn("Unexpected exception from an event executor: ", t);
} finally {
// 清理工作
// ...
}
}
});
}
关键点:
- 使用 CAS 保证线程只创建一次
- 线程创建后立即执行
run()方法,进入事件循环 - 事件循环是一个无限循环,直到 EventLoop 关闭
12.2 任务队列的处理
// SingleThreadEventExecutor.runAllTasks()
protected boolean runAllTasks(long timeoutNanos) {
// 1. 从 scheduledTaskQueue 中取出到期的定时任务,放入 taskQueue
fetchFromScheduledTaskQueue();
// 2. 从 taskQueue 中取出第一个任务
Runnable task = pollTask();
if (task == null) {
afterRunningAllTasks();
return false;
}
// 3. 计算截止时间
final long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos;
long runTasks = 0;
long lastExecutionTime;
// 4. 循环执行任务
for (;;) {
// 执行任务
safeExecute(task);
runTasks ++;
// 每执行 64 个任务,检查一次时间
if ((runTasks & 0x3F) == 0) {
lastExecutionTime = ScheduledFutureTask.nanoTime();
if (lastExecutionTime >= deadline) {
break; // 超时,退出
}
}
// 取下一个任务
task = pollTask();
if (task == null) {
lastExecutionTime = ScheduledFutureTask.nanoTime();
break;
}
}
afterRunningAllTasks();
this.lastExecutionTime = lastExecutionTime;
return true;
}
关键点:
- 支持定时任务(scheduledTaskQueue)
- 支持普通任务(taskQueue)
- 通过 timeoutNanos 限制执行时间,避免任务执行时间过长
- 每执行 64 个任务检查一次时间,这是一个性能优化
12.3 IO 事件的处理
// NioEventLoop.processSelectedKeys()
private void processSelectedKeys() {
if (selectedKeys != null) {
// 使用优化的 SelectedKeys
processSelectedKeysOptimized();
} else {
// 使用普通的 SelectedKeys
processSelectedKeysPlain(selector.selectedKeys());
}
}
优化版本的处理:
private void processSelectedKeysOptimized() {
for (int i = 0; i < selectedKeys.size; ++i) {
final SelectionKey k = selectedKeys.keys[i];
// 清空数组位置,帮助 GC
selectedKeys.keys[i] = null;
// 获取 attachment,这是我们注册时附加的 NioChannel
final Object a = k.attachment();
if (a instanceof AbstractNioChannel) {
// 处理这个 Channel 的事件
processSelectedKey(k, (AbstractNioChannel) a);
} else {
@SuppressWarnings("unchecked")
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
processSelectedKey(k, task);
}
// 检查是否需要再次 select
if (needsToSelectAgain) {
selectedKeys.reset(i + 1);
selectAgain();
i = -1;
}
}
}
处理单个 Key:
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
// 检查 Key 是否有效
if (!k.isValid()) {
unsafe.close(unsafe.voidPromise());
return;
}
try {
int readyOps = k.readyOps();
// 处理 OP_CONNECT 事件
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
// 处理 OP_WRITE 事件
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
ch.unsafe().forceFlush();
}
// 处理 OP_READ 或 OP_ACCEPT 事件
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}
关键点:
- 使用优化的数组遍历,比 HashSet 的迭代器快
- 处理完后清空数组位置,帮助 GC
- 根据不同的事件类型,调用不同的处理方法
- 异常处理:如果 Key 被取消,关闭 Channel
十五、总结与回顾
15.1 核心知识点总结
1. NioEventLoopGroup 的本质
- 是 Netty 的线程池实现
- 管理多个 NioEventLoop 实例
- 通过 Chooser 实现负载均衡
2. NioEventLoop 的本质
- 是单线程的事件循环
- 包含一个 Thread、一个 Selector、一个 taskQueue
- 负责处理 IO 事件和非 IO 任务
3. 关键设计理念
- 单线程模型:避免锁竞争,保证顺序性
- Reactor 模式:一个线程管理多个连接
- 延迟创建:线程在第一次使用时才创建
- Channel 绑定:一个 Channel 只绑定到一个 EventLoop
4. 核心参数
nThreads:线程数,默认 CPU 核心数 * 2ioRatio:IO 和非 IO 任务的时间比例,默认 50maxPendingTasks:任务队列容量,默认 16
5. 性能优化
- 优化 Selector 的 selectedKeys 数据结构
- 解决 JDK 的 epoll bug
- 使用 MPSC 队列
- 支持零拷贝
15.3 关键代码位置
核心类:
- io.netty.channel.nio.NioEventLoopGroup
- io.netty.channel.nio.NioEventLoop
- io.netty.channel.MultithreadEventLoopGroup
- io.netty.util.concurrent.MultithreadEventExecutorGroup
- io.netty.util.concurrent.SingleThreadEventExecutor
关键方法:
- NioEventLoopGroup 构造方法
- MultithreadEventExecutorGroup.newChild()
- SingleThreadEventExecutor.execute()
- SingleThreadEventExecutor.startThread()
- NioEventLoop.run()
- NioEventLoop.processSelectedKeys()
- NioEventLoop.runAllTasks()
15.4 延伸阅读
- Reactor 模式:理解事件驱动的设计模式
- Java NIO:理解 Selector、Channel、Buffer 的使用
- 线程模型:对比不同的线程模型(单线程、多线程、主从 Reactor)
- 无锁编程:理解 CAS、MPSC 队列等无锁技术
附录:完整的类图关系
EventExecutorGroup (接口)
↑
EventLoopGroup (接口)
↑
AbstractEventExecutorGroup (抽象类)
↑
MultithreadEventExecutorGroup (抽象类)
├── children: EventExecutor[] // 核心数组
├── chooser: EventExecutorChooser // 选择器
└── terminationFuture: Promise // 终止 Future
↑
MultithreadEventLoopGroup (抽象类)
└── DEFAULT_EVENT_LOOP_THREADS // 默认线程数
↑
NioEventLoopGroup (实现类)
└── newChild() 方法 // 创建 NioEventLoop
---
EventExecutor (接口)
↑
EventLoop (接口)
↑
AbstractEventExecutor (抽象类)
↑
SingleThreadEventExecutor (抽象类)
├── thread: Thread // 工作线程
├── executor: Executor // 线程创建器
├── taskQueue: Queue<Runnable> // 任务队列
└── state: int // 状态
↑
SingleThreadEventLoop (抽象类)
└── tailTasks: Queue<Runnable> // 尾部任务队列
↑
NioEventLoop (实现类)
├── selector: Selector // NIO Selector
├── provider: SelectorProvider // Selector 提供者
├── selectStrategy: SelectStrategy // 选择策略
└── ioRatio: int // IO 比例
1019

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



