java nio中selectedkeys()返回内部可变set而非新集合,因其实现为复用的hashset,用于累计未处理就绪键;必须用iterator.remove()手动移除已处理key,否则重复触发事件且不自动清理。

通过 Selector.selectedKeys() 获取就绪的 SelectionKey 集合后,不能直接遍历并处理——必须手动移除已处理的键,否则它会一直留在集合中,导致重复触发同一事件。
为什么 selectedKeys() 返回的是 Set 而不是新集合?
selectedKeys() 返回的是一个内部可变的 HashSet(或其子类),它由 Selector 自动维护:当通道就绪时,对应 Key 被加入;但不会自动清除。所以你每次调用它,拿到的是“累计未处理”的就绪键。
- 这个 Set 是线程不安全的,多线程操作需同步(通常只在单线程的 I/O 线程中使用)
- 它不是每次调用都新建对象,而是复用同一个实例,因此不能靠“新建遍历”来避免干扰
- 如果不显式清理,一次就绪事件可能被反复处理多次
标准处理流程(必须移除已处理的 Key)
典型写法如下:
用于 inference.sh 的 JavaScript/TypeScript SDK,可运行 AI 应用、构建代理、集成 150+ 模型。包名:@inferencesh/sdk(npm install),完整 TypeScript 支持。
while (selector.select() > 0) {
Set<selectionkey> selected = selector.selectedKeys();
Iterator<selectionkey> iter = selected.iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
iter.remove(); // ✅ 关键:立即移除,防止重复处理
if (key.isAcceptable()) {
handleAccept(key);
} else if (key.isConnectable()) {
handleConnect(key);
} else if (key.isReadable()) {
handleRead(key);
} else if (key.isWritable()) {
handleWrite(key);
}
}
}</selectionkey></selectionkey>
注意:remove() 必须在处理完当前 key 后立刻调用,且只能通过 Iterator.remove(),不能用 set.remove(key)——因为后者可能引发 ConcurrentModificationException(JDK 早期版本)或行为不一致。
Key 失效后会被自动从 selectedKeys 中移除吗?
不会自动移除。即使你调用了 key.cancel() 或通道关闭导致 key 无效,该 key 仍会保留在 selectedKeys() 中,直到你手动调用 iter.remove() 或下一次 select() 调用前由 Selector 内部清理(仅限已 cancel 的 key,在下次 select 时才真正剔除)。所以主动 remove 才是唯一可靠方式。
常见误区与建议
- 不要用 for-each 遍历
selectedKeys()并在循环内 remove —— 会抛异常 - 不要在处理逻辑里忘记 remove,尤其分支较多时(比如 catch 异常后跳过 remove)
- 如果处理耗时较长,考虑把 key 暂存到队列,交由其他线程处理,但要确保原线程仍完成 remove
- 调试时可打印 key.interestOps() 和 key.readyOps(),确认实际就绪的是哪些操作
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










