Netty源码分析篇:NioEventLoopGroup分析

NioEventLoopGroup 源码深度解析

一、什么是 NioEventLoopGroup?

在学习 Netty 的时候,我们经常会看到这样的代码:

EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();

那么 NioEventLoopGroup 到底是什么呢?

简单来说,NioEventLoopGroup 是 Netty 中的线程池实现。但它和我们熟悉的 ThreadPoolExecutor 不太一样,它是专门为事件驱动的 NIO 模型设计的线程池。

1.1 用一个比喻来理解

你可以把 NioEventLoopGroup 想象成一个餐厅的服务员团队

  • NioEventLoopGroup = 整个服务员团队(线程池)
  • NioEventLoop = 每个服务员(单个线程)
  • Channel = 每张餐桌(客户端连接)

当有客户(客户端)来用餐时,餐厅经理(Chooser)会分配一个服务员(NioEventLoop)来负责这张桌子。这个服务员会一直服务这张桌子,直到客户离开。

1.2 NioEventLoopGroup 的核心特点

  1. 固定数量的线程:创建时就确定了有多少个 NioEventLoop(线程)
  2. 每个线程独立工作:每个 NioEventLoop 有自己的任务队列和 Selector
  3. 负载均衡:通过 Chooser 将新连接分配给不同的 NioEventLoop
  4. 线程绑定:一个 Channel 绑定到一个 NioEventLoop 后就不会改变

二、NioEventLoopGroup 的构造方法

打开 NioEventLoopGroup 的源码,可以看到它有多个构造方法。作为初学者,我们只需要关注最常用的几个:

2.1 常用的构造方法

// 1. 无参构造(最常用)
public NioEventLoopGroup() {
    this(0);  // 传入 0,会使用默认线程数
}

// 2. 指定线程数(推荐使用)
public NioEventLoopGroup(int nThreads) {
    this(nThreads, (Executor) null);
}

// 3. 指定线程数和 Executor(高级用法)
public NioEventLoopGroup(int nThreads, Executor executor) {
    this(nThreads, executor, SelectorProvider.provider());
}

// 4. 指定线程数、Executor 和 SelectorProvider(更高级)
public NioEventLoopGroup(int nThreads, Executor executor, 
                         final SelectorProvider selectorProvider) {
    this(nThreads, executor, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);
}

// 5. 参数最全的构造方法(源码内部使用)
public NioEventLoopGroup(int nThreads, Executor executor, 
                         final SelectorProvider selectorProvider,
                         final SelectStrategyFactory selectStrategyFactory) {
    super(nThreads, executor, selectorProvider, selectStrategyFactory, 
          RejectedExecutionHandlers.reject());
}

2.2 构造参数详解

看到这么多参数,是不是有点懵?别担心,我们一个一个来理解:

参数名作用通俗解释
nThreads线程数量决定创建多少个 NioEventLoop(服务员数量)
Executor线程创建器注意:不是线程池!只是用来创建线程的工具
EventExecutorChooserFactory线程选择器工厂决定如何从线程池中选择一个线程(如何分配服务员)
SelectorProviderSelector 提供者为每个 NioEventLoop 创建 Selector(NIO 的核心组件)
SelectStrategy选择策略决定是阻塞等待还是立即返回
RejectedExecutionHandler拒绝策略当任务队列满了怎么办
children 数组核心数据结构存储所有 NioEventLoop 实例的数组

2.3 初学者应该怎么用?

对于初学者来说,记住这两种用法就够了:

// 方式1:使用默认线程数(CPU核心数 * 2)
NioEventLoopGroup group = new NioEventLoopGroup();

// 方式2:指定线程数(推荐)
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);      // Boss 只需要 1 个线程
NioEventLoopGroup workerGroup = new NioEventLoopGroup(4);    // Worker 可以多个线程

三、NioEventLoopGroup 的初始化过程

现在我们来看看,当你 new NioEventLoopGroup() 的时候,Netty 内部到底做了什么。

整个初始化过程可以分为 7 个步骤,我们一步一步来看:

步骤1:确定线程数量(nThreads)

// 在 MultithreadEventLoopGroup 中
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
    // 如果 nThreads 为 0,使用默认值
    super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}

// DEFAULT_EVENT_LOOP_THREADS 的计算逻辑
static {
    DEFAULT_EVENT_LOOP_THREADS = Math.max(1, 
        SystemPropertyUtil.getInt("io.netty.eventLoopThreads", 
        NettyRuntime.availableProcessors() * 2));
}

通俗解释:

  • 如果你传入 0 或者不传参数,Netty 会自动计算线程数
  • 默认线程数 = CPU 核心数 × 2
  • 比如你的电脑是 4 核 CPU,那么默认会创建 8 个线程
  • 你也可以通过系统属性 io.netty.eventLoopThreads 来指定

为什么是 CPU 核心数 × 2?

因为 NIO 是 IO 密集型操作,线程经常会阻塞在 IO 上,所以可以创建比 CPU 核心数更多的线程来提高吞吐量。

步骤2:创建线程创建器(Executor)

// 在 MultithreadEventExecutorGroup 中
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
    // 如果没有传入 executor,创建默认的 ThreadPerTaskExecutor
    this(nThreads, executor == null ? null : new ThreadPerTaskExecutor(executor), args);
}

// 如果 executor 为 null,会创建默认的
if (executor == null) {
    executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}

ThreadPerTaskExecutor 的实现:

public final class ThreadPerTaskExecutor implements Executor {
    private final ThreadFactory threadFactory;

    public ThreadPerTaskExecutor(ThreadFactory threadFactory) {
        this.threadFactory = threadFactory;
    }

    @Override
    public void execute(Runnable command) {
        // 每次执行任务都创建一个新线程
        threadFactory.newThread(command).start();
    }
}

通俗解释:

  • Executor 不是线程池,它只是一个线程创建器
  • ThreadPerTaskExecutor 的作用是:每次需要线程时,就创建一个新线程
  • 这个 Executor 会为每个 NioEventLoop 创建一个专属的工作线程
  • 创建出来的线程名字类似:nioEventLoopGroup-2-1nioEventLoopGroup-2-2

为什么不用线程池?

因为每个 NioEventLoop 只需要一个固定的线程,这个线程会一直运行,不需要复用。

步骤3:创建线程选择器(EventExecutorChooser)

// 在 MultithreadEventExecutorGroup 中
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
    this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
}

DefaultEventExecutorChooserFactory 的实现:

public final class DefaultEventExecutorChooserFactory implements EventExecutorChooserFactory {
    
    @Override
    public EventExecutorChooser newChooser(EventExecutor[] executors) {
        // 判断线程数是否是 2 的幂次方
        if (isPowerOfTwo(executors.length)) {
            // 如果是 2 的幂次方,使用位运算优化
            return new PowerOfTwoEventExecutorChooser(executors);
        } else {
            // 否则使用普通的取模运算
            return new GenericEventExecutorChooser(executors);
        }
    }
    
    private static boolean isPowerOfTwo(int val) {
        return (val & -val) == val;
    }
}

两种 Chooser 的实现:

// 1. 位运算优化版本(当线程数是 2 的幂次方时使用)
private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
    private final AtomicInteger idx = new AtomicInteger();
    private final EventExecutor[] executors;

    @Override
    public EventExecutor next() {
        // 使用位运算代替取模,性能更好
        return executors[idx.getAndIncrement() & executors.length - 1];
    }
}

// 2. 普通取模版本
private static final class GenericEventExecutorChooser implements EventExecutorChooser {
    private final AtomicInteger idx = new AtomicInteger();
    private final EventExecutor[] executors;

    @Override
    public EventExecutor next() {
        // 使用取模运算
        return executors[Math.abs(idx.getAndIncrement() % executors.length)];
    }
}

通俗解释:

  • Chooser 的作用是:从线程池中选择一个线程来处理新连接
  • 选择策略是轮询(Round-Robin):0 → 1 → 2 → 3 → 0 → 1 → …
  • 如果线程数是 2 的幂次方(2、4、8、16…),使用位运算优化,速度更快
  • 否则使用普通的取模运算

举个例子:

假设你创建了 4 个线程(2 的幂次方):

第 1 个连接 → idx=0 → 0 & 3 = 0 → 选择线程 0
第 2 个连接 → idx=1 → 1 & 3 = 1 → 选择线程 1
第 3 个连接 → idx=2 → 2 & 3 = 2 → 选择线程 2
第 4 个连接 → idx=3 → 3 & 3 = 3 → 选择线程 3
第 5 个连接 → idx=4 → 4 & 3 = 0 → 选择线程 0(循环)

步骤4:创建 Selector 提供者(SelectorProvider)

public NioEventLoopGroup(int nThreads, Executor executor) {
    // 使用系统默认的 SelectorProvider
    this(nThreads, executor, SelectorProvider.provider());
}

在 NioEventLoop 中的使用:

// 每个 NioEventLoop 创建时,会使用 SelectorProvider 创建自己的 Selector
Selector selector = selectorProvider.openSelector();

通俗解释:

  • SelectorProvider 是 Java NIO 提供的类,用于创建 Selector
  • 每个 NioEventLoop 都有自己独立的 Selector
  • Selector 是 NIO 的核心,用于监听多个 Channel 的 IO 事件
  • 通常我们使用系统默认的 SelectorProvider 就可以了

步骤5:创建选择策略(SelectStrategy)

public NioEventLoopGroup(int nThreads, Executor executor, 
                         final SelectorProvider selectorProvider) {
    this(nThreads, executor, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);
}

DefaultSelectStrategyFactory 的实现:

final class DefaultSelectStrategyFactory implements SelectStrategyFactory {
    static final SelectStrategyFactory INSTANCE = new DefaultSelectStrategyFactory();

    @Override
    public SelectStrategy newSelectStrategy() {
        return DefaultSelectStrategy.INSTANCE;
    }
}

DefaultSelectStrategy 的实现:

final class DefaultSelectStrategy implements SelectStrategy {
    static final SelectStrategy INSTANCE = new DefaultSelectStrategy();

    @Override
    public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) throws Exception {
        // 如果有任务,执行非阻塞的 selectNow(),立即返回
        // 如果没有任务,返回 SELECT 标记,后续会执行阻塞的 select()
        return hasTasks ? selectSupplier.get() : SelectStrategy.SELECT;
    }
}

通俗解释:

  • SelectStrategy 决定了 NioEventLoop 的工作模式
  • 如果任务队列有任务:执行 selectNow()(非阻塞,立即返回)
  • 如果任务队列没有任务:执行 select()(阻塞等待 IO 事件)
  • 这样可以在处理 IO 事件和执行任务之间取得平衡

为什么要这样设计?

  • 如果一直阻塞在 select() 上,任务队列的任务就得不到及时处理
  • 如果一直执行 selectNow(),会浪费 CPU 资源
  • 所以根据是否有任务来动态选择,既能及时处理任务,又不会浪费 CPU

步骤6:创建拒绝策略(RejectedExecutionHandler)

public NioEventLoopGroup(int nThreads, Executor executor, 
                         final SelectorProvider selectorProvider,
                         final SelectStrategyFactory selectStrategyFactory) {
    super(nThreads, executor, selectorProvider, selectStrategyFactory, 
          RejectedExecutionHandlers.reject());  // 使用默认的拒绝策略
}

RejectedExecutionHandlers 的实现:

public final class RejectedExecutionHandlers {
    
    // 直接拒绝,抛出异常
    public static RejectedExecutionHandler reject() {
        return REJECT;
    }
    
    private static final RejectedExecutionHandler REJECT = new RejectedExecutionHandler() {
        @Override
        public void rejected(Runnable task, SingleThreadEventExecutor executor) {
            throw new RejectedExecutionException("Task " + task.toString() + 
                                                 " rejected from " + executor.toString());
        }
    };
}

通俗解释:

  • 每个 NioEventLoop 都有一个任务队列(默认容量是无限的)
  • 如果队列满了(或者 EventLoop 已经关闭),新任务会被拒绝
  • 默认的拒绝策略是:直接抛出 RejectedExecutionException 异常
  • 这和 ThreadPoolExecutor 的拒绝策略类似

步骤7:创建 children 数组并初始化(核心)

这是整个初始化过程中最核心的部分!所有参数设置完成后,会进入这个方法:

protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                        EventExecutorChooserFactory chooserFactory, 
                                        Object... args) {
    // ========== 1. 参数校验 ==========
    if (nThreads <= 0) {
        throw new IllegalArgumentException(
            String.format("nThreads: %d (expected: > 0)", nThreads));
    }

    // ========== 2. 创建默认的 Executor ==========
    if (executor == null) {
        executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
    }

    // ========== 3. 创建 children 数组(核心数据结构)==========
    children = new EventExecutor[nThreads];

    // ========== 4. 循环创建每一个 NioEventLoop 实例 ==========
    for (int i = 0; i < nThreads; i ++) {
        boolean success = false;
        try {
            // 调用 newChild() 方法创建 NioEventLoop
            // 这个方法在 NioEventLoopGroup 中被覆写
            children[i] = newChild(executor, args);
            success = true;
        } catch (Exception e) {
            throw new IllegalStateException("failed to create a child event loop", e);
        } finally {
            // ========== 5. 如果创建失败,需要清理已经创建的 NioEventLoop ==========
            if (!success) {
                // 关闭已经创建成功的 EventLoop
                for (int j = 0; j < i; j ++) {
                    children[j].shutdownGracefully();
                }

                // 等待它们完全终止
                for (int j = 0; j < i; j ++) {
                    EventExecutor e = children[j];
                    try {
                        while (!e.isTerminated()) {
                            e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
                        }
                    } catch (InterruptedException interrupted) {
                        Thread.currentThread().interrupt();
                        break;
                    }
                }
            }
        }
    }

    // ========== 6. 创建 Chooser,用于从 children 数组中选择 EventLoop ==========
    chooser = chooserFactory.newChooser(children);

    // ========== 7. 设置终止监听器 ==========
    final FutureListener<Object> terminationListener = new FutureListener<Object>() {
        @Override
        public void operationComplete(Future<Object> future) throws Exception {
            // 当所有 EventLoop 都终止时,设置 terminationFuture 为成功
            if (terminatedChildren.incrementAndGet() == children.length) {
                terminationFuture.setSuccess(null);
            }
        }
    };
    
    // 给每个 EventLoop 添加终止监听器
    for (EventExecutor e: children) {
        e.terminationFuture().addListener(terminationListener);
    }

    // ========== 8. 创建只读的 children 集合 ==========
    Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
    Collections.addAll(childrenSet, children);
    readonlyChildren = Collections.unmodifiableSet(childrenSet);
}

让我们逐步解析这个方法:

3.1 参数校验
if (nThreads <= 0) {
    throw new IllegalArgumentException(
        String.format("nThreads: %d (expected: > 0)", nThreads));
}

很简单,线程数必须大于 0。

3.2 创建 children 数组
children = new EventExecutor[nThreads];

这是 NioEventLoopGroup 的核心数据结构,用于存储所有的 NioEventLoop 实例。

比如你创建了 4 个线程:

children[0] = NioEventLoop-0
children[1] = NioEventLoop-1
children[2] = NioEventLoop-2
children[3] = NioEventLoop-3
3.3 循环创建 NioEventLoop
for (int i = 0; i < nThreads; i ++) {
    children[i] = newChild(executor, args);
}

newChild() 方法在 NioEventLoopGroup 中的实现:

@Override
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
    // args[0] = SelectorProvider
    // args[1] = SelectStrategyFactory
    // args[2] = RejectedExecutionHandler
    return new NioEventLoop(this, executor, (SelectorProvider) args[0],
        ((SelectStrategyFactory) args[1]).newSelectStrategy(),
        (RejectedExecutionHandler) args[2]);
}

通俗解释:

  • 循环 nThreads 次,每次创建一个 NioEventLoop
  • 每个 NioEventLoop 都会被赋予:
    • 父 Group(this)
    • 线程创建器(executor)
    • Selector 提供者(selectorProvider)
    • 选择策略(selectStrategy)
    • 拒绝策略(rejectedExecutionHandler)
3.4 异常处理机制
finally {
    if (!success) {
        // 关闭已经创建成功的 EventLoop
        for (int j = 0; j < i; j ++) {
            children[j].shutdownGracefully();
        }
        // 等待它们完全终止
        for (int j = 0; j < i; j ++) {
            EventExecutor e = children[j];
            try {
                while (!e.isTerminated()) {
                    e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
                }
            } catch (InterruptedException interrupted) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }
}

通俗解释:

  • 如果创建 NioEventLoop 的过程中出现异常,需要清理已经创建的 EventLoop
  • 比如创建了 3 个,第 4 个失败了,那么需要关闭前 3 个
  • 这是一种资源清理机制,防止资源泄漏
3.5 创建 Chooser
chooser = chooserFactory.newChooser(children);

创建线程选择器,用于从 children 数组中选择 NioEventLoop。

3.6 设置终止监听器
final FutureListener<Object> terminationListener = new FutureListener<Object>() {
    @Override
    public void operationComplete(Future<Object> future) throws Exception {
        if (terminatedChildren.incrementAndGet() == children.length) {
            terminationFuture.setSuccess(null);
        }
    }
};

for (EventExecutor e: children) {
    e.terminationFuture().addListener(terminationListener);
}

通俗解释:

  • 给每个 NioEventLoop 添加一个监听器
  • 当一个 NioEventLoop 终止时,terminatedChildren 计数器加 1
  • 当所有 NioEventLoop 都终止时,设置 terminationFuture 为成功
  • 这样外部就可以通过 terminationFuture 来等待整个 Group 关闭
3.7 创建只读集合
Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
Collections.addAll(childrenSet, children);
readonlyChildren = Collections.unmodifiableSet(childrenSet);

创建一个只读的 Set,防止外部修改 children 数组。

四、NioEventLoopGroup 的核心方法

初始化完成后,NioEventLoopGroup 就可以使用了。我们来看看它的核心方法:

4.1 next() - 选择一个 EventLoop

@Override
public EventLoop next() {
    return (EventLoop) chooser.next();
}

通俗解释:

  • 使用 Chooser 从 children 数组中选择一个 NioEventLoop
  • 选择策略是轮询(Round-Robin)
  • 每次调用 next() 都会返回下一个 EventLoop

使用场景:

// 当有新连接时,选择一个 EventLoop 来处理
EventLoop eventLoop = workerGroup.next();
eventLoop.register(channel);

4.2 register() - 注册 Channel

@Override
public ChannelFuture register(Channel channel) {
    return next().register(channel);
}

通俗解释:

  • 选择一个 EventLoop,然后将 Channel 注册到这个 EventLoop 上
  • 一旦注册,这个 Channel 就会一直由这个 EventLoop 处理

4.3 shutdownGracefully() - 优雅关闭

@Override
public Future<?> shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit) {
    // 关闭所有的 EventLoop
    for (EventLoop l: children) {
        l.shutdownGracefully(quietPeriod, timeout, unit);
    }
    return terminationFuture();
}

通俗解释:

  • 关闭所有的 NioEventLoop
  • 每个 EventLoop 会先处理完队列中的任务,然后再关闭
  • 返回一个 Future,可以用来等待所有 EventLoop 关闭完成

五、完整的初始化流程图

让我们用一张图来总结整个初始化过程:

new NioEventLoopGroup(4)
         |
         v
步骤1: 确定线程数 = 4
         |
         v
步骤2: 创建 ThreadPerTaskExecutor
         |
         v
步骤3: 创建 PowerOfTwoEventExecutorChooser (因为 4 是 2 的幂次方)
         |
         v
步骤4: 创建 SelectorProvider
         |
         v
步骤5: 创建 DefaultSelectStrategy
         |
         v
步骤6: 创建 RejectedExecutionHandler
         |
         v
步骤7: 创建 children 数组并初始化
         |
         +---> 创建 NioEventLoop-0
         |       |
         |       +---> 创建 Selector-0
         |       +---> 创建 TaskQueue-0
         |
         +---> 创建 NioEventLoop-1
         |       |
         |       +---> 创建 Selector-1
         |       +---> 创建 TaskQueue-1
         |
         +---> 创建 NioEventLoop-2
         |       |
         |       +---> 创建 Selector-2
         |       +---> 创建 TaskQueue-2
         |
         +---> 创建 NioEventLoop-3
                 |
                 +---> 创建 Selector-3
                 +---> 创建 TaskQueue-3

六、总结

通过这篇文章,我们深入学习了 NioEventLoopGroup 的源码实现:

  1. NioEventLoopGroup 是什么:Netty 的线程池实现,专为 NIO 设计
  2. 核心组件:children 数组、Chooser、Executor、SelectorProvider 等
  3. 初始化过程:7 个步骤,从参数设置到创建 NioEventLoop
  4. 核心方法:next()、register()、shutdownGracefully()
  5. 设计思想:固定线程数、线程绑定、轮询选择

希望这篇文章能帮助你理解 NioEventLoopGroup 的源码实现。如果你想深入学习,建议继续阅读 NioEventLoop 的源码,了解单个线程是如何工作的。


推荐阅读:

  • NioEventLoop 运行机制详解
  • Netty 服务端启动流程详解
  • Netty Channel 注册流程详解

如果这篇文章对你有帮助,欢迎点赞、收藏、分享!

本文详细介绍了 NioEventLoopGroup 的源码实现,更多 Netty 源码解析请查看 NioEventLoopGroup深度解析.md 文档。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值