SelectionKey 是通道与 Selector 的绑定凭证,需准确识别就绪事件、绑定业务附件、安全遍历 selectedKeys、防御性检查有效性并主动清理。

利用 SelectionKey 处理就绪事件,是构建高性能 Java NIO 服务的核心环节。它不是简单地“判断一下状态”,而是要结合事件注册、键生命周期管理、线程安全与异常恢复,形成一套稳定、低开销、可长期运行的处理引擎。关键在于:每个 SelectionKey 是通道与 Selector 的绑定凭证,承载着事件类型、附件数据、就绪状态和取消逻辑——用好它,才能真正释放单线程多路复用的性能红利。
准确识别并分发就绪事件类型
SelectionKey 提供 isAcceptable()、isConnectable()、isReadable()、isWritable() 四个布尔方法,对应四种标准就绪事件。必须严格按注册时设定的兴趣集(interestOps)来响应,避免误判:
- ServerSocketChannel 只应响应 OP_ACCEPT,收到后立即 accept() 新连接,并将新 SocketChannel 注册为 OP_READ
- SocketChannel 在连接建立后,通常先注册 OP_READ;需要发送数据时才临时注册 OP_WRITE(写完立刻取消,避免持续触发)
- 不要在 isReadable() 为 true 时调用 write(),也不要在 isWritable() 为 true 时调用 read() —— 事件类型与操作必须严格匹配
绑定业务上下文到 SelectionKey 的 attachment
每个 SelectionKey 支持 attach(Object) 方法,这是实现无锁状态传递的关键。把 Channel 对应的会话对象(如 Session、ConnectionContext)、读写缓冲区(ByteBuffer)、协议解析器等直接绑定过去,避免在事件处理中反复查找或创建:
- 注册时绑定:
key.attach(new Session(channel)) - 处理时取出:
Session session = (Session) key.attachment() - 推荐使用轻量、无状态的对象,避免 attachment 持有长生命周期资源(如未关闭的流)
安全遍历与及时清理 selectedKeys 集合
Selector.selectedKeys() 返回的是一个可变集合,其迭代器不支持并发修改。必须在每次 select() 返回后,用 Iterator 遍历并显式 remove() 已处理的 key,否则该 key 会在下一轮继续出现,引发重复处理甚至数据错乱:
- 务必使用
Iterator<selectionkey> iter = selector.selectedKeys().iterator()</selectionkey> - 每处理完一个 key,立刻调用
iter.remove(),不能用 for-each 循环 - 若在处理过程中发生异常(如客户端断连),仍需确保 remove() 执行,可用 try-finally 包裹
主动管理 SelectionKey 生命周期与异常恢复
SelectionKey 不是“一劳永逸”的句柄,它可能被取消、失效或关联通道已关闭。必须在事件处理中防御性检查,并主动清理:
- 处理前先验证:
if (!key.isValid()) continue; - 捕获 IOException(如远程关闭)后,调用
key.cancel()并close()对应 Channel - 遇到 CancelledKeyException,说明 key 已被其他线程取消,跳过本次处理即可
- 对频繁空轮询(连续 select() 返回 0)做计数,超阈值(如 512 次)时重建 Selector,规避 epoll bug


















