java nio 的 selector 基于操作系统 i/o 多路复用(如 epoll/kqueue)实现单线程管理大量连接,通过注册非阻塞 channel、select() 阻塞等待内核事件通知、遍历 selectedkeys() 分发处理,需手动移除 key 并避免耗时操作。

Java NIO 的 Selector 通过操作系统底层的 I/O 多路复用机制(如 Linux 的 epoll、macOS 的 kqueue、Windows 的 IOCP 模拟),让一个线程能同时监听多个 Channel 的就绪事件,从而实现单线程管理成百上千个连接。
核心原理:基于事件就绪通知
Selector 不是轮询每个 Channel 是否可读/可写,而是将 Channel 注册到 Selector 后,由内核在对应事件(如数据到达、连接建立、写缓冲就绪)发生时主动通知。JVM 将注册请求转为系统调用(如 epoll_ctl),把文件描述符和关注事件加入内核事件表;调用 select() 或 selectNow() 时,实际执行的是 epoll_wait() 等系统调用,阻塞等待或立即返回就绪事件列表。
关键步骤:注册 + 轮询 + 处理
-
Channel 必须配置为非阻塞模式:调用
configureBlocking(false),否则注册会抛异常 -
通过
channel.register(selector, ops, attachment)注册:指定关注的操作类型(SelectionKey.OP_READ、OP_ACCEPT等),并可附带业务对象(如 ByteBuffer、Handler) -
调用
selector.select()阻塞等待就绪事件:返回就绪 Channel 的数量,触发selectedKeys()集合更新 -
遍历
selectedKeys()集合处理事件:对每个SelectionKey判断其readyOps(),分发给对应逻辑(如OP_ACCEPT时调用serverChannel.accept()获取新连接)
避免常见陷阱
-
处理完事件后必须手动调用
keyIterator.remove():否则该 key 会一直留在集合中,导致重复处理 - 不要在事件处理器中执行耗时操作(如数据库查询、大文件读写):会阻塞整个 Selector 线程,影响其他 Channel 响应;应将任务提交到业务线程池
-
注册新 Channel 时,确保它已正确配置且未关闭:例如客户端连接成功后,再注册到共享 Selector;服务端
accept()得到的 SocketChannel 也需先设为非阻塞再注册 - 注意 OP_WRITE 的使用场景:它几乎总是就绪(只要发送缓冲区有空闲),一般只在写半包或写失败后才显式关注,写完立即取消关注,避免空转
典型单线程事件循环结构
一个典型的 NIO 服务端主循环长这样:
- 初始化 Selector 和 ServerSocketChannel
- ServerSocketChannel 注册
OP_ACCEPT - 进入
while (running) { selector.select(); Set<selectionkey> keys = selector.selectedKeys(); Iterator<selectionkey> iter = keys.iterator(); while (iter.hasNext()) { process(key); iter.remove(); } }</selectionkey></selectionkey> - 每个
process(key)根据 key 的 channel 类型和就绪操作做分支处理
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











