Netty 的线程模型是怎么样的?
一则或许对你有用的小广告
欢迎加入小哈的星球,你将获得:专属的实战项目(4个项目都能学) / 1v1 提问 / 简历修改 / Java 学习路线 / 社群讨论 / 学习打卡 / 每月赠书
《Spring AI 项目实战(问答机器人、RAG 智能客服、联网搜索)》已完结,基于
Spring AI + Spring Boot 3.x + JDK 21...,查看介绍《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...,查看介绍;演示链接:http://116.62.199.48:7070/《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接:http://116.62.199.48/
新开坑项目:《从零手撸:秒杀系统高并发优化实战》 正在更新中...,查看介绍
截止目前,星球内专栏累计输出 150w+ 字,讲解图 5110+ 张,还在持续爆肝中.. 后续还会上新更多项目,已有 4700+ 小伙伴加入学习,欢迎点击围观
面试考察点
- 架构认知:面试官不只是想听你说出 “Reactor” 这个词,更想知道你是否清楚 Netty 是怎么把 Reactor 模式落地到代码里的——
EventLoop、EventLoopGroup、BossGroup/WorkerGroup这几个核心组件各司其职。 - 原理深度:考察你对 “一个
Channel绑定一个EventLoop、一个EventLoop处理多个Channel” 这种串行化无锁设计的理解,能不能讲清楚为什么 Netty 这么设计。 - 生产应用:能不能根据业务场景选择合适的线程模型(单线程 / 多线程 / 主从多线程),以及为什么生产环境绝大多数都是 “主从 Reactor 多线程” 这套组合。
核心答案
一句话先给结论:Netty 的线程模型基于 Reactor 模式,支持三种配置——单线程模型、多线程模型、主从多线程模型(也叫主从 Reactor),生产环境基本都用主从 Reactor。
| 线程模型 | 核心结构 | 适用场景 | 生产推荐 |
|---|---|---|---|
| 单线程模型 | 1 个线程干所有活 | 连接数极少、QPS 极低 | ❌ 不推荐 |
| 多线程模型 | 1 个 Acceptor 线程 + N 个 IO 线程 | 客户端场景、连接数中等 | ⚠️ 视场景 |
| 主从 Reactor 多线程 | N 个 Boss 线程 + M 个 Worker 线程 | 服务端、高并发 | ✅ 强烈推荐 |
注意 Netty 默认的服务端启动代码(两个 EventLoopGroup)就是主从 Reactor 多线程模型。
深度解析
一、Reactor 模式回顾
Netty 的线程模型根源是 Doug Lea 大神的 Reactor 模式。Reactor 模式本质上就是 “事件驱动 + 多路复用 + 线程池” 这三件套。它把 “等待事件” 和 “处理事件” 解耦:
- Reactor(反应器):负责监听事件(连接、读、写),分发事件
- Handler(处理器):负责真正处理事件
- Acceptor:处理新连接的接收
Reactor 模式有三种形态,对应到 Netty 就是上面表格里那三种线程模型。
二、Netty 的主从 Reactor 多线程模型
直接上图,看一张就懂:
整体执行流程我拆开讲:
- BossGroup 专门负责接收新连接:它内部有若干个
NioEventLoop,轮询OP_ACCEPT事件。一旦有新客户端连进来,Boss 线程就从 OS 的全连接队列里 Accept 出新连接(TCP 三次握手由内核完成),把产生的SocketChannel注册到 WorkerGroup 里某个NioEventLoop的 selector 上。 - WorkerGroup 负责处理 IO 读写:Worker 线程轮询
OP_READ、OP_WRITE事件,数据读到ByteBuf后,丢给ChannelPipeline里一串ChannelHandler处理(编解码、业务逻辑等)。 - 绑定关系是关键:一个
Channel一辈子只绑定一个NioEventLoop,但一个NioEventLoop可以绑定多个Channel。这就保证了单个 Channel 的所有 IO 操作都在同一个线程里完成,天然无锁。
三、NioEventLoop 的内部结构
NioEventLoop 是 Netty 线程模型的真正主角,它本质是一个 “单线程执行器 + Selector + 任务队列” 三合一。
NioEventLoop.run() 里的死循环就干三件事:
- select:调用
selector.select(timeout)等 IO 事件就绪 - processSelectedKeys:处理就绪的 IO 事件(读、写、连接)
- runAllTasks:执行
taskQueue里的普通任务和scheduledTaskQueue里的定时任务
Netty 为了避免 “空轮询 bug”(JDK epoll 100% CPU 那个坑),会在内部检测 select 触发次数,超过阈值(默认 SELECTOR_AUTO_REBUILD_THRESHOLD,512 次)就重建 Selector。
四、串行化无锁设计
这是 Netty 性能炸裂的关键之一,必须讲。
- 每个
Channel绑定一个固定的NioEventLoop - 这个 Channel 的所有 IO 操作(read、write、connect)和 ChannelHandler 的回调,全部由绑定的那个 EventLoop 线程执行
- 既然都在同一个线程里,那
ChannelPipeline里多个ChannelHandler之间共享数据就不需要加锁
换句话说,Netty 用 “绑定 + 串行执行” 这一套把多线程竞争给绕开了。这套设计叫做 “串行化无锁化设计”。当然,如果你的 ChannelHandler 里自己又起了线程池,那就打破这个保证了——所以 Netty 提供了 addLast(EventExecutorGroup, handlers...) 这种重载,允许你把耗时业务逻辑切到独立的业务线程池跑,避免阻塞 IO 线程。
五、三种线程模型在 Netty 里怎么配
代码示例,一看就懂:
// 服务端启动器
EventLoopGroup bossGroup = new NioEventLoopGroup(1); // Boss 线程数固定 1 即可
EventLoopGroup workerGroup = new NioEventLoopGroup(); // Worker 默认 = CPU 核心数 * 2
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup) // 传两个 Group —— 主从 Reactor
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new MyDecoder(), new MyHandler());
}
});
三种模型的区别主要就在 group(...) 怎么传:
- 单线程模型:
bootstrap.group(group),且group只 1 个线程,Boss 和 Worker 共用 - 多线程模型:
bootstrap.group(group),group是多线程的,Boss 和 Worker 共用一个多线程 Group - 主从多线程模型:
bootstrap.group(bossGroup, workerGroup),两个独立的 Group
老规矩:BossGroup 的线程数一般给 1 就够了,因为一个端口只需要一个 ServerSocketChannel 接收连接。WorkerGroup 默认值是
CPU 核心数 * 2,Netty 源码里就是这么算的(MultithreadEventExecutorGroup默认nThreads = CPU 核心数 * 2)。
面试高频追问
-
追问一:BossGroup 一定要设置成 1 个线程吗?
不一定。单端口监听的情况下 1 个够用;但如果你监听多个端口(比如同时开 8080 和 8443),BossGroup 线程数就要 >= 2。生产里我一般直接给 1,单端口完全够。
-
追问二:为什么 Netty 的 IO 线程不能做耗时操作?
因为一个
NioEventLoop上绑定了多个 Channel,IO 线程一旦被某个 Channel 的业务逻辑卡住,其他 Channel 的 IO 事件就全部阻塞了,相当于一个连接拖垮所有连接。解决办法是业务 handler 里把耗时任务丢到独立的业务线程池(DefaultEventExecutorGroup)执行。 -
追问三:Netty 怎么解决 JDK 的空轮询 bug?
通过
NioEventLoop内部对 select 触发次数做检测,在没超时却提前返回的情况下计数,超过阈值(默认 512)就判定为空轮询,新建一个 Selector 把旧的 Channel 注册信息迁移过去。
常见面试变体
- “Netty 的
EventLoop和EventLoopGroup是什么关系?” - “Netty 主从 Reactor 模式中,Boss 和 Worker 分别负责什么?”
- “Netty 的
NioEventLoop内部都做了什么?” - “Netty 为什么不用锁?怎么保证线程安全?”
记忆口诀
主从 Reactor 三段式:Boss 接连接 → Worker 干读写 → Channel 绑定 EventLoop 串行跑。
一句话串起来:一个 Channel 一辈子绑定一个 EventLoop,一个 EventLoop 一辈子可以服务多个 Channel。
总结
Netty 的线程模型本质就是 Reactor 模式的工程化落地,主从 Reactor 多线程是生产标配。核心抓住三点就稳:Boss 接连接 / Worker 处理读写 / Channel 与 EventLoop 一对一绑定实现串行无锁。再把 NioEventLoop 的 select → processSelectedKeys → runAllTasks 这条循环说清楚,面试官基本就要点头了。
