java中selector通过nio非阻塞i/o让单线程轮询多个channel就绪事件;仅非阻塞selectablechannel可注册,需configureblocking(false)后调用register指定op_accept/connect/read/write;select()获取就绪key后须遍历selectedkeys()、依readyops()分发处理并iterator.remove();支持attach附件存业务上下文,且selector非线程安全,须单线程操作,唤醒用wakeup(),关闭需cancel key、close channel再close selector。

Java 中通过 Selector 统一管理多个网络通道状态,核心在于使用 NIO 的非阻塞 I/O 模型,让单个线程轮询多个 Channel 的就绪事件(如连接、读、写、接受),避免为每个连接分配独立线程。
注册 Channel 到 Selector 并设置兴趣事件
只有非阻塞模式的 SelectableChannel(如 SocketChannel、ServerSocketChannel)才能注册到 Selector。注册前必须调用 configureBlocking(false),再通过 register(Selector, int ops) 指定关注的事件类型:
-
SelectionKey.OP_ACCEPT:服务端等待新客户端连接 -
SelectionKey.OP_CONNECT:客户端等待连接完成(非阻塞 connect 后) -
SelectionKey.OP_READ:通道有数据可读(或对端关闭) -
SelectionKey.OP_WRITE:通道可写(通常总是就绪,慎用,易忙等)
例如服务端监听:
serverChannel.configureBlocking(false);
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
轮询就绪事件并分发处理
调用 selector.select()(阻塞直到有事件)、selectNow()(立即返回)或 select(long timeout) 获取就绪的 SelectionKey 集合。每次循环需遍历 selectedKeys(),根据 key 的 readyOps() 或 isAcceptable()/isReadable() 等方法判断事件类型,再做对应操作:
- 若
isAcceptable():调用serverChannel.accept()得到新SocketChannel,设为非阻塞并注册OP_READ - 若
isReadable():从对应SocketChannel读取数据(注意检查返回值是否为 -1,表示对端关闭) - 若
isWritable():执行写操作后,通常应取消注册OP_WRITE,避免空转;待缓冲区有空间再重新注册
处理完每个 key 后,务必调用 iterator.remove() 清除已处理项,否则下次 select() 会重复返回。
绑定业务上下文与附件(Attachment)
SelectionKey 支持绑定任意对象作为附件(attach(Object)),常用于保存该通道对应的业务状态,比如用户 ID、会话对象、读写缓冲区等。这样在事件处理时无需额外映射查找:
- 注册时附加:
key.attach(new Session(buffer)) - 处理时获取:
Session session = (Session) key.attachment();
附件是线程安全的(仅被单个 selector 线程访问),适合存放轻量级上下文,避免全局 Map 查找开销。
注意唤醒、关闭与线程安全
Selector 不是线程安全的,所有操作(注册、取消、select)应由同一线程执行。若其他线程需中断当前 select() 阻塞,可调用 selector.wakeup()。正常关闭流程为:
- 取消所有 key:
key.cancel() - 关闭所有关联 channel
- 调用
selector.close()
取消 key 后,它会在下一次 select() 时被清理;直接 close channel 也会自动取消其 key。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











