Netty源码分析---NioEventLoopGroup深度解析

相关文章链接

位运算详解

waken方法详解

ThreadPerTaskExecutor与线程创建详解

processSelectedKeys() vs runAllTasks()

NioServerSocketChannel-Unsafe初始化详解

NioEventLoop的run方法详解

NioEventLoopGroup深度解析

inEventLoop方法详解

executionMask详解

Netty源码分析–认真系列(一)

Netty源码分析–认真系列(二)

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_READOP_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;
    }
}

两种选择策略:

  1. 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];
    }
}
  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 自己,作为 parent
    • executor:ThreadPerTaskExecutor,用于创建线程
    • SelectorProvider:用于创建 Selector
    • SelectStrategy:选择策略
    • 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 的作用:

  1. 存储用户提交的任务(通过 execute(Runnable) 方法)
  2. 存储 Netty 内部的任务(如 register、bind、connect 等)
  3. 在事件循环中,会定期从这个队列中取出任务执行
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();
}

优雅关闭的流程:

  1. 停止接收新任务
  2. 等待已提交的任务执行完成
  3. 在 quietPeriod 时间内,如果没有新任务,则关闭
  4. 如果超过 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() 方法

八、核心参数总结表

参数默认值作用调优建议
nThreadsCPU 核心数 * 2线程池大小bossGroup 设为 1,workerGroup 根据压测调整
ExecutorThreadPerTaskExecutor创建线程通常不需要自定义
ChooserFactoryDefaultEventExecutorChooserFactory选择 EventLoop不需要自定义
SelectorProvider平台默认实现创建 Selector不需要自定义
SelectStrategyFactoryDefaultSelectStrategyFactory选择策略不需要自定义
RejectedExecutionHandler抛出异常任务拒绝策略可以自定义为记录日志
ioRatio50IO 和非 IO 任务时间比根据业务调整,IO 密集型可以调高
maxPendingTasks16任务队列初始容量任务多时可以调大

十、深入理解:为什么这样设计?

10.1 为什么 NioEventLoop 是单线程的?

传统多线程模型的问题:

// 假设多个线程处理同一个 Channel
Thread-1: 读取数据 -> 处理 -> 写入响应
Thread-2: 读取数据 -> 处理 -> 写入响应
// 问题:需要加锁,性能下降,而且可能乱序

Netty 的单线程模型:

// 一个 Channel 绑定到一个 EventLoop
EventLoop-1: 读取数据 -> 处理 -> 写入响应
// 优势:无需加锁,保证顺序,性能更高

单线程模型的优势:

  1. 无锁化:同一个 Channel 的操作都在一个线程中,不需要加锁
  2. 顺序性:保证事件的处理顺序
  3. 简化编程:不需要考虑线程安全问题
  4. 高性能:避免了锁竞争和上下文切换

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 {
                // 清理工作
                // ...
            }
        }
    });
}

关键点:

  1. 使用 CAS 保证线程只创建一次
  2. 线程创建后立即执行 run() 方法,进入事件循环
  3. 事件循环是一个无限循环,直到 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;
}

关键点:

  1. 支持定时任务(scheduledTaskQueue)
  2. 支持普通任务(taskQueue)
  3. 通过 timeoutNanos 限制执行时间,避免任务执行时间过长
  4. 每执行 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());
    }
}

关键点:

  1. 使用优化的数组遍历,比 HashSet 的迭代器快
  2. 处理完后清空数组位置,帮助 GC
  3. 根据不同的事件类型,调用不同的处理方法
  4. 异常处理:如果 Key 被取消,关闭 Channel

十五、总结与回顾

15.1 核心知识点总结

1. NioEventLoopGroup 的本质
  • 是 Netty 的线程池实现
  • 管理多个 NioEventLoop 实例
  • 通过 Chooser 实现负载均衡
2. NioEventLoop 的本质
  • 是单线程的事件循环
  • 包含一个 Thread、一个 Selector、一个 taskQueue
  • 负责处理 IO 事件和非 IO 任务
3. 关键设计理念
  • 单线程模型:避免锁竞争,保证顺序性
  • Reactor 模式:一个线程管理多个连接
  • 延迟创建:线程在第一次使用时才创建
  • Channel 绑定:一个 Channel 只绑定到一个 EventLoop
4. 核心参数
  • nThreads:线程数,默认 CPU 核心数 * 2
  • ioRatio:IO 和非 IO 任务的时间比例,默认 50
  • maxPendingTasks:任务队列容量,默认 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 延伸阅读

  1. Reactor 模式:理解事件驱动的设计模式
  2. Java NIO:理解 Selector、Channel、Buffer 的使用
  3. 线程模型:对比不同的线程模型(单线程、多线程、主从 Reactor)
  4. 无锁编程:理解 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 比例

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值