Netty源码阅读:(二)Reactor线程模型

Reactor线程模型



前言

Reactor 线程模型是 Netty 高性能的关键要素之一。
这篇文章记录学习netty的reactor线程模型源码的过程,学习过程通过阅读netty源代码,debug不断调试,以及结合书籍《跟闪电侠学Netty》。内容可能会比较枯燥,阅读源代码本身就是一件比较枯燥的事情。


一、NioEventLoopGroup 继承关系

EventLoopGroup bossGroup = new NioEventLoopGroup();

从我们创建EventLoopGroup作为我们的切入点。我们可以先了解它的具体继承关系,它的分层继承在功能上比较清晰分明,但是如果分开来看,就容易造成困扰,因此,我们可以先整体梳理一下。它的核心继承关系如下所示。

AbstractEventExecutorGroup (implements EventExecutorGroup)
     └── MultithreadEventExecutorGroup
          └── MultithreadEventLoopGroup
               └── NioEventLoopGroup
  1. EventExecutorGroup(接口):定义事件执行器组的通用行为,管理一组 EventExecutor。
    核心方法:
    • 定义 EventExecutor next(): 获取下一个执行器。
      EventExecutor next();
      
    • 定义 Future<?> shutdownGracefully(): 优雅关闭
      Future<?> shutdownGracefully();
      
  2. AbstractEventExecutorGroup (抽象类):提供 EventExecutorGroup 的默认实现,将方法调用委托给 next() 返回的执行器。
    • 实现submit()方法:
      @Override
      public Future<?> submit(Runnable task) {
          return next().submit(task);
      }
      
    • 实现execute()方法:
      @Override
      public void execute(Runnable command) {
          next().execute(command);
      }
      
  3. 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);
      
  4. 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);
      }
      
  5. 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);
      }
      

二、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 线程模型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值