不会。cancel()仅标记键为已取消,仍保留在已注册键集合中,直到下次select()或selectNow()时才真正清理;isValid()立即返回false,但当前selectedKeys()中可能仍存在。

SelectionKey.cancel 会立即移除监听吗?
不会。调用 cancel() 只是把键标记为“已取消”,它仍保留在选择器的已注册键集合中,直到下一次 select() 或 selectNow() 调用期间,选择器才会真正清理它。这意味着:如果你在事件处理中调用 cancel(),当前轮次的 selectedKeys() 里它可能还在,甚至可能再次被返回(取决于实现和 JVM 版本),但它的 isValid() 会立刻返回 false。
- 务必在调用
cancel()后检查key.isValid(),避免对已取消键继续调用interestOps()或attach() - 不要依赖“取消后立刻消失”来跳过处理 —— 正确做法是在遍历
selectedKeys()时先if (!key.isValid()) continue; - 如果在多线程环境操作同一
SelectionKey,cancel()是线程安全的,但后续对键状态的判断需注意竞态(比如刚 cancel,另一线程还没读到isValid()变化)
为什么调用 cancel() 后通道还收到事件?
常见于未及时清理已取消键的迭代逻辑。典型错误是直接遍历 selector.selectedKeys() 并在循环体内调用 key.cancel(),却不做 Iterator.remove() 或跳过无效键:
for (SelectionKey key : selector.selectedKeys()) {
if (key.isReadable()) {
handleRead(key);
key.cancel(); // ❌ 键已取消,但仍在本次 selectedKeys 集合中
}
}
下一轮 select() 前,这个已取消键仍可能被返回(尤其在 Linux epoll 下有缓存行为)。正确做法是:
- 使用
Iterator遍历时显式remove():it.remove(); - 或统一在处理完所有有效键后,再批量清理(不推荐,易漏)
- 更稳妥的是:每次循环开头加
if (!key.isValid()) continue;
cancel() 和关闭通道(close)的顺序重要吗?
重要。必须先调用 key.cancel(),再调用通道的 close()。否则可能出现:
- 通道关闭后,选择器在下次
select()中检测到就绪但找不到对应有效键,抛出CancelledKeyException(虽然 JDK 通常静默处理,但某些场景会暴露) - 若先 close 通道,部分系统(如 Windows)可能触发底层异常,影响选择器稳定性
-
key.channel().close()会隐式调用该键的cancel(),但仅限该键 —— 如果一个通道注册了多个键(比如同时注册到多个 Selector),其他键不会被自动取消
标准释放流程应为:key.cancel(); key.channel().close();
取消后还能恢复监听吗?
不能。cancel() 是不可逆操作。一旦调用,该 SelectionKey 永远失效,isValid() 永远返回 false,且无法重新关联到原通道或新选择器。
- 想重新监听,必须重新调用
channel.register(selector, ops)获取新SelectionKey - 如果只是临时禁用读事件,改用
key.interestOps(0)或清除对应位(如key.interestOps(key.interestOps() & ~SelectionKey.OP_READ))更合适 - 注意:
interestOps(0)不等于取消 —— 键依然有效,通道仍注册在选择器中,只是当前无兴趣事件
真正需要“取消”的场景其实不多,多数时候只是动态调整 interestOps;cancel() 应只用于彻底退出监听、释放资源的终点动作。










