Reactor线程模型
文章目录
前言
Reactor 线程模型是 Netty 高性能的关键要素之一。
这篇文章记录学习netty的reactor线程模型源码的过程,学习过程通过阅读netty源代码,debug不断调试,以及结合书籍《跟闪电侠学Netty》。内容可能会比较枯燥,阅读源代码本身就是一件比较枯燥的事情。
一、NioEventLoopGroup 继承关系
EventLoopGroup bossGroup = new NioEventLoopGroup();
从我们创建EventLoopGroup作为我们的切入点。我们可以先了解它的具体继承关系,它的分层继承在功能上比较清晰分明,但是如果分开来看,就容易造成困扰,因此,我们可以先整体梳理一下。它的核心继承关系如下所示。
AbstractEventExecutorGroup (implements EventExecutorGroup)
└── MultithreadEventExecutorGroup
└── MultithreadEventLoopGroup
└── NioEventLoopGroup
EventExecutorGroup
(接口):定义事件执行器组的通用行为,管理一组 EventExecutor。
核心方法:- 定义 EventExecutor next(): 获取下一个执行器。
EventExecutor next();
- 定义 Future<?> shutdownGracefully(): 优雅关闭
Future<?> shutdownGracefully();
- 定义 EventExecutor next(): 获取下一个执行器。
AbstractEventExecutorGroup
(抽象类):提供 EventExecutorGroup 的默认实现,将方法调用委托给 next() 返回的执行器。- 实现submit()方法:
@Override public Future<?> submit(Runnable task) { return next().submit(task); }
- 实现execute()方法:
@Override public void execute(Runnable command) { next().execute(command); }
- 实现submit()方法:
MultithreadEventExecutorGroup
(抽象类):管理多线程环境下的 EventExecutor 实例池。- 承担了非常重要的初始化责任,创建和保存了EventExecutor数组和EventExecutorChooser。
private final EventExecutor[] children; private final Set<EventExecutor> readonlyChildren; private final AtomicInteger terminatedChildren = new AtomicInteger(); private final Promise<?> terminationFuture = new DefaultPromise(GlobalEventExecutor.INSTANCE); private final EventExecutorChooserFactory.EventExecutorChooser chooser;
- 实现next()方法
@Override public EventExecutor next() { return chooser.next(); }
- 实现了shutdownGracefully的相关方法。
@Override public Future<?> shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit) { for (EventExecutor l: children) { l.shutdownGracefully(quietPeriod, timeout, unit); } return terminationFuture(); }
- 定义抽象方法 newChild() :创建具体的执行器实例
protected abstract EventExecutor newChild(Executor executor, Object... args);
- 承担了非常重要的初始化责任,创建和保存了EventExecutor数组和EventExecutorChooser。
MultithreadEventLoopGroup
(抽象类):扩展为专门处理 I/O 事件的循环组。- 初始化计算了线程池的线程数量:如果系统变量没有设置,则取netty可使用的核心数的两倍
private static final int DEFAULT_EVENT_LOOP_THREADS; static { DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt( "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2 ) ); }
- 实现EventLoopGroup接口,实现了register()系列方法。
@Override public ChannelFuture register(Channel channel) { return next().register(channel); }
- 初始化计算了线程池的线程数量:如果系统变量没有设置,则取netty可使用的核心数的两倍
NioEventLoopGroup
(具体实现):基于 Java NIO 的具体实现。由于功能等层层分配,NioEventLoopGroup中的代码就比较简单,只有130行。比较重要的方法有:- 实现newChild()的方法,具体的EventLoop实现由该方法决定,这里使用的是NioEventLoop,创建并且返回。NioEventLoop在后面再介绍。
@Override 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); }
- 实现newChild()的方法,具体的EventLoop实现由该方法决定,这里使用的是NioEventLoop,创建并且返回。NioEventLoop在后面再介绍。
二、NioEventLoopGroup的创建流程
在创建NioEventLoopGroup的过程中,它会层层往上调用父类的构造方法,传递一些参数,以及给定一些默认参数,比较简单,我们直接来到最核心的代码(简化版):
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
if (executor == null) {
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
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) {
···
} finally {
if (!success) {
// 优雅地关闭
}
}
}
chooser = chooserFactory.newChooser(children);
// 创建监听器监听线程终止事件
···
for (EventExecutor e: children) {
e.terminationFuture().addListener(terminationListener);
}
// 创建children的只读视图
Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
Collections.addAll(childrenSet, children);
readonlyChildren = Collections.unmodifiableSet(childrenSet);
}
其中最重要,值得关注的是有三个函数:
2.1 ThreadPerTaskExecutor
这里我们先看newDefaultThreadFactory(),它返回了一个DefaultThreadFactory对象,该对象是一个工厂方法,那么该对象最重要的就是创建对象的能力了,那么,它创建的是什么对象呢?请看newThread()方法。
// DefaultThreadFactory.java
@Override
public Thread newThread(Runnable r) {
Thread t = newThread(FastThreadLocalRunnable.wrap(r), prefix + nextId.incrementAndGet());
···
return t;
}
protected Thread newThread(Runnable r, String name) {
return new FastThreadLocalThread(threadGroup, r, name);
}
这里简单来说,可以通过newThread来创建FastThreadLocalThread对象,这个实体是经过 Netty 优化之后的,比jdk要快。更复杂的内容,这里先不谈。
ThreadPerTaskExecutor又做了什么呢?
public final class ThreadPerTaskExecutor implements Executor {
private final ThreadFactory threadFactory;
public ThreadPerTaskExecutor(ThreadFactory threadFactory) {
if (threadFactory == null) {
throw new NullPointerException("threadFactory");
}
this.threadFactory = threadFactory;
}
@Override
public void execute(Runnable command) {
threadFactory.newThread(command).start();
}
}
它做的事情比较简单,把我们之前看到的线程工厂DefaultThreadFactory保存起来,并且它的execute方法在执行的时候,会创建一个新的线程,该线程由线程工厂的newThread方法来创建,并启动,在这里就是创建FastThreadLocalThread对象。该execute方法在什么时候被调用呢,请看newChild()方法。
2.2 newChild;
根据前面对NioEventLoopGroup的继承的分析,我们已经知道了,该方法的具体实现在NioEventLoopGroup中
// NioEventLoopGroup
@Override
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);
}
它负责创建并返回一个新的 NioEventLoop 实例(即I/O事件循环线程)。我们先观察到它的构造函数如下:
NioEventLoop( NioEventLoopGroup parent,
Executor executor,
SelectorProvider selectorProvider,
SelectStrategy strategy,
RejectedExecutionHandler rejectedExecutionHandler,
EventLoopTaskQueueFactory queueFactory)
这个parent传入的是this,就是指的自己(NioEventLoopGroup),executor指的是ThreadPerTaskExecutor,selectorProvider是SelectorProvider.provide()的结果,这个和具体的操作系统相关,strategy是DefaultSelectStrategyFactory(),这里的rejectedExecutionHandler是RejectedExecutionHandlers,queueFactory这里传递的null。至此我们已经可以创建NioEventLoop实例了,关于NioEventLoop的具体实现细节将在后续中讲解。
2.3 newChooser;
chooser是NioEventLoopGroup在分配任务的选择策略,在它的next()方法中,会调用chooser的next()方法,来选择NioEventLoop。
// MultithreadEventExecutorGroup.java
@Override
public EventExecutor next() {
return chooser.next();
}
这里的chooser使用的是DefaultSelectStrategyFactory.INSTANCE,这里DefaultSelectStrategyFactory.INSTANCE使用的是单例模式。
// DefaultEventExecutorChooserFactory.java
public final class DefaultEventExecutorChooserFactory implements EventExecutorChooserFactory {
···
@Override
public EventExecutorChooser newChooser(EventExecutor[] executors) {
if (isPowerOfTwo(executors.length)) {
return new PowerOfTwoEventExecutorChooser(executors);
} else {
return new GenericEventExecutorChooser(executors);
}
}
private static boolean isPowerOfTwo(int val) {
return (val & -val) == val;
}
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[Math.abs(idx.getAndIncrement() % executors.length)];
}
}
}
在这个代码中,我省略来构造函数,构造函数会传入executors并保存起来,重要的是看代码做了什么,因此省略其他代码。这里DefaultEventExecutorChooserFactory提供了两种Chooser,PowerOfTwoEventExecutorChooser和GenericEventExecutorChooser。
- GenericEventExecutorChooser听名字,是一个普通的Chooser,它的做法也很普通,就是ID自增,对长度取模。
- PowerOfTwoEventExecutorChooser则是Netty的优化版本,使用它的条件则是,执行器的个数是2的次方。PowerOfTowEventExecutorChooser 的具体实现则是通过位运算实现,比如,如果 CPU 核数为 4,默认创建 8 个 NioEventLoop,符合 2 的幂,这里 executors.length-1 计算出来是 7,也就是二进制数的 111,那么,只要使用一个自增的 ID,和 111 进行与运算,就 能实现循环的效果,而与运算是操作系统底层支持的,要比取模运算效率高很多。由此,我们也 可以感受到,为了性能优化,Netty 在细节上面考虑得确实非常细致。
三、NioEventLoop
这里有几个比较重要的父类:
3.1 AbstractScheduledEventExecutor
扩展定时任务调度能力。
public abstract class AbstractScheduledEventExecutor extends AbstractEventExecutor {
private static final Comparator<ScheduledFutureTask<?>> SCHEDULED_FUTURE_TASK_COMPARATOR =
new Comparator<ScheduledFutureTask<?>>() {
@Override
public int compare(ScheduledFutureTask<?> o1, ScheduledFutureTask<?> o2) {
return o1.compareTo(o2);
}
};
PriorityQueue<ScheduledFutureTask<?>> scheduledTaskQueue;
PriorityQueue<ScheduledFutureTask<?>> scheduledTaskQueue() {
if (scheduledTaskQueue == null) {
scheduledTaskQueue = new DefaultPriorityQueue<ScheduledFutureTask<?>>(
SCHEDULED_FUTURE_TASK_COMPARATOR,
// Use same initial capacity as java.util.PriorityQueue
11);
}
return scheduledTaskQueue;
}
创建调度任务的队列,并使用Netty精心优化的DefaultPriorityQueue。并且实现了和schedule相关的一些核心方法。
protected final long nextScheduledTaskNano() {
Queue<ScheduledFutureTask<?>> scheduledTaskQueue = this.scheduledTaskQueue;
ScheduledFutureTask<?> scheduledTask = scheduledTaskQueue == null ? null : scheduledTaskQueue.peek();
if (scheduledTask == null) {
return -1;
}
return Math.max(0, scheduledTask.deadlineNanos() - nanoTime());
}
<V> ScheduledFuture<V> schedule(final ScheduledFutureTask<V> task) {
if (inEventLoop()) {
scheduledTaskQueue().add(task);
} else {
execute(new Runnable() {
@Override
public void run() {
scheduledTaskQueue().add(task);
}
});
}
return task;
}
3.2 SingleThreadEventExecutor
实现单线程事件循环的核心抽象类,提供了高效、线程安全的事件处理机制。
public abstract class SingleThreadEventExecutor extends AbstractScheduledEventExecutor implements OrderedEventExecutor {
···
private static final int ST_NOT_STARTED = 1;
private static final int ST_STARTED = 2;
private static final int ST_SHUTTING_DOWN = 3;
private static final int ST_SHUTDOWN = 4;
private static final int ST_TERMINATED = 5;
private final Queue<Runnable> taskQueue;
private volatile Thread thread;
private final Executor executor;
private volatile boolean interrupted;
private final CountDownLatch threadLock = new CountDownLatch(1);
private final Set<Runnable> shutdownHooks = new LinkedHashSet<Runnable>();
private final boolean addTaskWakesUp;
private final int maxPendingTasks;
private final RejectedExecutionHandler rejectedExecutionHandler;
private volatile int state = ST_NOT_STARTED;
···
}
该类的设计其实非常精妙,感兴趣的可以自己查看源代码。从上述定义的属性,可以看到SingleThreadEventExecutor维护了一个任务队列(在初始化时会创建一个LinkedBlockingQueue),以及一个thread及其状态记录。
有几个核心的方法:
- inEventLoop()方法在netty代码中经常出现,判断当前线程是否为事件循环线程,决定是否需要同步操作,在事件循环线程内执行的操作无需加锁,提升性能
@Override public boolean inEventLoop(Thread thread) { return thread == this.thread; }
- execute():首次提交任务时懒启动线程
@Override public void execute(Runnable task) { ··· boolean inEventLoop = inEventLoop(); addTask(task); if (!inEventLoop) { startThread(); ··· } // 唤醒事件循环 if (!addTaskWakesUp && wakesUpForTask(task)) { wakeup(inEventLoop); } }
- runAllTasks():执行任务队列中的所有可执行任务,并在指定时间限制内返回。此方法通过批量处理任务和时间片控制,平衡了任务执行效率与事件循环响应性。定时任务无法保证绝对准时执行,遵循 “尽力而为” 原则。
protected boolean runAllTasks(long timeoutNanos) { // 1. 将到期的定时任务从scheduledTaskQueue转移到普通任务队列 fetchFromScheduledTaskQueue(); 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 ++; // 5. 每执行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; }
- doStartThread():启动线程,其中SingleThreadEventExecutor.this.run()交由子类来实现。
private void doStartThread() { // 使用executor启动新线程执行事件循环 executor.execute(() -> { // 记录当前线程为事件循环线程 thread = Thread.currentThread(); try { // 执行事件循环主逻辑(由子类实现,如NioEventLoop.run()) SingleThreadEventExecutor.this.run(); } catch (Throwable t) { ··· } finally { // 1. 更新状态为关闭中 ··· // 2. 确认并执行优雅关闭 // 3. 资源清理 } }); }
3.3 NioEventLoop
基于 Java NIO Selector 的具体实现,处理网络 IO 事件
private Selector selector;
private Selector unwrappedSelector;
private SelectedSelectionKeySet selectedKeys;
private final SelectorProvider provider;
private final AtomicBoolean wakenUp = new AtomicBoolean();
private final SelectStrategy selectStrategy;
private volatile int ioRatio = 50;
private int cancelledKeys;
private boolean needsToSelectAgain;
它首先定义了一些基本的属性,尤其的定义和保存了一个Selector,它的父类SingleThreadEventExecutor保存了一个线程,所以简单说NioEventLoop就是一个线程和一个Selector构成的。
核心方法:
- run():是 Netty 非阻塞 IO 模型的核心实现,它构建了一个无限循环来处理网络 IO 事件和任务队列,是整个框架的 “心脏”。
- 监听网络连接的 IO 事件,处理就绪channel
- 执行用户提交的任务/定时调度任务
- 管理事件循环的生命周期
// 简化版本 @Override protected void run() { for (;;) { // 无限循环实现事件循环 try { // 1. 计算选择策略并执行Selector.select() switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) { case SelectStrategy.SELECT: select(wakenUp.getAndSet(false)); // 执行阻塞选择 if (wakenUp.get()) { selector.wakeup(); // 按需唤醒Selector } break; } // 2. 处理IO事件与任务调度 final int ioRatio = this.ioRatio; if (ioRatio == 100) { processSelectedKeys(); // 处理就绪Channel runAllTasks(); // 执行所有任务 } else { long ioStartTime = System.nanoTime(); processSelectedKeys(); // 处理就绪Channel long ioTime = System.nanoTime() - ioStartTime; runAllTasks(ioTime * (100 - ioRatio) / ioRatio); // 按比例分配任务执行时间 } } catch (IOException e) { rebuildSelector0(); // 重建Selector处理异常 handleLoopException(e); continue; } catch (Throwable t) { handleLoopException(t); // 统一处理循环内异常 } finally { // 3. 优雅关闭检查 if (isShuttingDown()) { closeAll(); // 关闭所有Channel if (confirmShutdown()) { // 确认关闭状态 return; } } } } }
- select():Netty 对Selector.select()的重构实现。有以下特点:
- 根据定时任务到期时间计算 select 超时时间,避免无意义阻塞,超时时间 ≤ 0 时自动切换为非阻塞 selectNow(),快速响应定时任务。
- 任务队列非空时,执行非阻塞select,能够及时响应任务。
- 解决Epoll 空轮询 BUG。
private void select(boolean oldWakenUp) throws IOException { Selector selector = this.selector; try { int selectCnt = 0; // 记录select调用次数 long currentTimeNanos = System.nanoTime(); long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos); // 计算select截止时间 for (;;) { // 循环执行select,直到满足退出条件 long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L; // 超时处理:timeoutMillis <= 0时执行非阻塞select if (timeoutMillis <= 0) { if (selectCnt == 0) { selector.selectNow(); // 非阻塞select } break; } // 任务队列非空时,执行非阻塞select if (hasTasks() && wakenUp.compareAndSet(false, true)) { selector.selectNow(); selectCnt = 1; break; } // 执行阻塞select int selectedKeys = selector.select(timeoutMillis); selectCnt++; // 满足以下任一条件则退出循环 if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) { break; } // 检测线程中断 if (Thread.interrupted()) { selectCnt = 1; break; } // 检测空轮询并重建Selector(核心优化点) long time = System.nanoTime(); if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) { selectCnt = 1; // 正常超时,重置计数器 } else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 && selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) { selector = selectRebuildSelector(selectCnt); // 重建Selector selectCnt = 1; break; } currentTimeNanos = time; } } catch (CancelledKeyException e) { // 处理取消的Key异常(JDK常见问题) } }
Epoll 空轮询 BUG
:在 Linux 系统中,JDK 的 EpollSelectorImpl 基于 epoll_wait () 实现 IO 多路复用,但在特定条件下(如网络异常或文件描述符异常关闭),可能触发 epoll_wait () 非预期返回的问题:即使没有就绪事件,epoll_wait () 也会立即返回 0,导致 Java 线程在无阻塞的情况下持续循环调用 select () 方法,最终使 CPU 使用率飙升至 100%。。
Netty解决方案
:Netty 通过智能检测 - 重建机制彻底解决了这一问题:通过维护 select () 返回 0 的连续次数计数器(selectCnt),结合系统时间推进情况进行判断。若 select () 在设定超时时间内返回 0 且系统时间正常推进,则视为正常超时;若系统时间未按预期推进,则判定可能发生空轮询。此时,Netty 会创建新的 Selector 实例,并将所有注册的 Channel 平滑迁移至新 Selector,同时关闭旧实例,从而恢复正常的事件监听流程。
四、NioEventLoop补充
4.1 SelectedSelectionKeySet
在 Linux 平台上,Java NIO 的 Selector 底层实现为 EpollSelectorImpl,其内部维护了两个关键的 Set 集合:selectedKeys 用于存储就绪的 SelectionKey,而 publicSelectedKeys 则作为只读视图对外提供访问。由于 JDK 默认使用 HashSet 实现这两个集合,在高并发场景下会存在哈希冲突和频繁扩容的性能开销。
Netty 通过自定义的 SelectedSelectionKeySet 类对这一机制进行了优化。该类基于数组实现,避免了哈希操作,并通过反射将 JDK Selector 内部的两个集合字段都替换为自定义实现,从而显著提升事件处理效率。以下是关键实现代码:
private SelectorTuple openSelector() {
// 获取 JDK Selector 实现类
final Class<?> selectorImplClass = (Class<?>) maybeSelectorImplClass;
// 创建 Netty 自定义的基于数组的集合实现
final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
// 通过特权操作反射修改 JDK 内部字段
Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
// 获取 JDK Selector 内部的两个集合字段
Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");
// 设置字段可访问
ReflectionUtil.trySetAccessible(selectedKeysField, true);
ReflectionUtil.trySetAccessible(publicSelectedKeysField, true);
// 将两个字段都设置为 Netty 自定义的集合实例
selectedKeysField.set(unwrappedSelector, selectedKeySet);
publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);
return null;
} catch (Exception e) {
// 处理反射异常
return e;
}
}
});
// 保存对自定义集合的引用,用于后续事件处理
selectedKeys = selectedKeySet;
// 其他初始化逻辑...
}
由于Selector中的Set被替换成了SelectedSelectionKeySet,当 IO 事件就绪时,Selector 会调用add方法将 SelectionKey 添加到SelectedSelectionKeySet中。这是 JDK Selector 的内部实现逻辑。
final class SelectedSelectionKeySet extends AbstractSet<SelectionKey> {
private SelectionKey[] keys; // 核心存储数组
private int size; // 当前元素数量
@Override
public boolean add(SelectionKey o) {
if (o == null) {
return false;
}
keys[size++] = o;
if (size == keys.length) {
increaseCapacity();
}
return true;
}
@Override
public int size() {
return size;
}
void reset(int start) {
Arrays.fill(keys, start, size, null);
size = 0;
}
}
具体操作SelectedSelectionKeySet则是:
/**
* 优化版就绪事件处理方法,直接操作底层数组提升性能
* 通过数组索引遍历避免哈希表迭代开销,适用于高并发场景
*/
private void processSelectedKeysOptimized() {
// 遍历selectedKeys数组中的所有就绪事件
for (int i = 0; i < selectedKeys.size; ++i) {
// 从数组中获取当前SelectionKey
final SelectionKey k = selectedKeys.keys[i];
// 清空数组对应位置的引用,避免重复处理
selectedKeys.keys[i] = null;
// 获取SelectionKey绑定的附件对象(通常是Channel或任务)
final Object a = k.attachment();
// 根据附件类型选择处理逻辑
if (a instanceof AbstractNioChannel) {
// 处理NIO Channel相关事件
processSelectedKey(k, (AbstractNioChannel) a);
} else {
// 处理NIO任务(如连接操作)
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
processSelectedKey(k, task);
}
// 检查是否需要重新执行select操作
if (needsToSelectAgain) {
// 重置已处理的事件索引
selectedKeys.reset(i + 1);
// 重新执行select获取新的就绪事件
selectAgain();
// 重置循环索引以便重新遍历新事件
i = -1;
}
}
}
对于每个 NioEventLoop 而言,每隔 256 个 Channel 从 Selector 上移除的时候,就 标记 needsToSelectAgain 为 true,这么做的目的应该是每隔 256 次连接断开,重新清理一下 SelectionKeys,这相当于用批 量删除替代了 Selector 原 HashSet 数据结构的删除。
4.2 MPSC Queue
TaskQueue具体的实现类是LinkedBlockingQueue(线程安全)。但是在使用上是MPSC Queue,将外部线程的异步任务聚集,在 Reactor 线程内部用单线程来 批量执行以提升性能。
//AbstractChannelHandlerContext.java
private void write(Object msg, boolean flush, ChannelPromise promise) {
// ... 前置处理代码(如消息验证、引用计数检查等)
// 获取下一个 ChannelHandler 的执行器(通常是 EventLoop)
EventExecutor executor = next.executor();
// 判断当前线程是否是执行器所在的 EventLoop 线程
if (executor.inEventLoop()) {
// 当前线程是 EventLoop 线程,可直接执行写操作
if (flush) {
// 立即写入并刷新缓冲区
next.invokeWriteAndFlush(m, promise);
} else {
// 仅写入,不立即刷新(数据暂存于缓冲区)
next.invokeWrite(m, promise);
}
} else {
// 当前线程不是 EventLoop 线程,需要将写操作封装为任务提交到 EventLoop
AbstractWriteTask task;
if (flush) {
// 创建写入并刷新的任务
task = WriteAndFlushTask.newInstance(next, m, promise);
} else {
// 创建仅写入的任务
task = WriteTask.newInstance(next, m, promise);
}
// 安全地提交任务到执行器
safeExecute(executor, task, promise, m);
}
}
总结
Reactor 线程模型是一种高效的事件驱动编程模式,其核心思想是通过一个或多个线程专门处理事件分发,将 IO 操作与业务逻辑分离,从而提升系统的并发处理能力。NioEventLoopGroup 作为 Netty 中 Reactor 模型的具体实现,本质上是一组 NioEventLoop 的集合,负责管理和分配 EventLoop 线程,每个 NioEventLoop 对应一个独立的事件循环线程,用于处理注册在其上的 Channel 的 IO 事件及任务队列中的任务。NioEventLoop 内部维护了 Selector 用于监听 IO 事件,通过无限循环执行 select、processSelectedKeys 和 runAllTasks 等操作,实现对 Channel 的读写事件、连接事件等的处理,同时结合任务队列实现对用户提交任务的调度。Netty 对 NioEventLoop 的优化贯穿整个事件循环过程,包括解决 JDK Selector 的空轮询 BUG、通过 SelectedSelectionKeySet 优化事件存储结构、采用动态超时控制和任务优先调度策略等,使得 NioEventLoop 能够高效处理海量并发连接,而 NioEventLoopGroup 则通过合理的线程分配策略,如轮询选择 EventLoop,确保事件能够被均匀分配到各个线程,充分利用多核 CPU 资源,最终构建起高性能的 Reactor 线程模型。