必须手动移除已处理的SelectionKey,因为selectedKeys()是内部可变集合,select()不会自动清理已就绪但未移除的Key,否则会导致事件重复触发;应使用Iterator遍历并调用remove(),禁止增强for循环中直接remove或clear()。

在Java NIO中,Selector 的 selectedKeys() 集合必须在每次处理完就显式移除已处理的 SelectionKey,否则该 Key 会在下一次 select() 调用中被重复返回,导致事件被反复触发——这是常见且关键的使用陷阱。
为什么必须手动删除已处理的 SelectionKey
selectedKeys() 返回的是一个内部可变集合(通常是 HashSet),它不会自动清理。Selector 在每次调用 select() 后,会将新就绪的 Channel 对应的 Key 添加进去,但**不会移除之前已就绪、尚未被应用代码移除的 Key**。只要 Key 仍处于就绪状态(例如 Socket 还有数据可读),它就会持续出现在集合中。
如果不移除,同一个 Key 可能被反复处理,轻则浪费 CPU,重则引发逻辑错误(如多次解析同一段数据、重复关闭连接等)。
标准做法:遍历 + Iterator.remove()
必须使用 Iterator 遍历并调用 remove() 方法,这是唯一安全的方式。直接在增强 for 循环中调用 selectedKeys.remove(key) 会抛出 ConcurrentModificationException。
立即学习“Java免费学习笔记(深入)”;
- 获取迭代器:
Iterator<SelectionKey> it = selector.selectedKeys().iterator(); - 循环中调用
it.next()获取 Key,处理后立即调用it.remove() - 不要在循环外或别处调用
selectedKeys().clear()—— 这会清掉尚未处理但已加入的 Key,造成事件丢失
典型正确结构示例
以下是推荐的事件处理主循环骨架:
while (true) {
int n = selector.select(); // 阻塞等待就绪事件
if (n == 0) continue;
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
it.remove(); // ✅ 关键:立刻移除
try {
if (key.isAcceptable()) {
handleAccept(key);
} else if (key.isReadable()) {
handleRead(key);
} else if (key.isWritable()) {
handleWrite(key);
}
} catch (IOException e) {
key.cancel(); // 出错时取消 Key,防止后续干扰
key.channel().close();
}
}
}
注意 cancel() 和 close() 的时机
key.cancel() 是用于主动取消注册,通常在连接关闭或异常时调用;而 it.remove() 只是从 selectedKeys 集合中移除引用,不影响 Channel 或 Selector 注册关系。
- 正常处理完毕 → 必须
it.remove() - 连接断开或不可恢复错误 → 先
key.cancel(),再关闭 channel,最后it.remove()(或由 cancel 自动触发清理) - 不要仅靠
key.cancel()替代it.remove():cancel 不会从当前 selectedKeys 中移除该 Key,本次循环仍会看到它


















