cancelledkeyexception 由 jdk 在对已取消的 selectionkey 执行非法操作时抛出,需通过 isvalid() 预检避免;遍历时应使用 iterator.remove() 并及时 cancel 和 close 资源。

Selector 本身不会主动抛出 CancelledKeyException,这个异常是在你**对已取消的 SelectionKey 执行非法操作时**(比如调用 key.interestOps() 或 key.channel())由 JDK 内部检测并抛出的。因此,“捕获”它不是 Selector 的责任,而是你代码中**安全使用 SelectionKey 的责任。
为什么 CancelledKeyException 会冒出来?
常见触发场景:
- 在
selectedKeys()遍历中,另一个线程调用了key.cancel(),而你后续又访问了该 key 的属性或方法 - 遍历时未及时从
selectedKeys()中移除已处理的 key(虽然不直接导致 cancel,但可能延长 key 生命周期,增加竞态风险) - Channel 关闭(如
socketChannel.close())自动导致关联 key 被取消,但你的代码仍尝试复用该 key
正确做法:避免触发,而不是被动捕获
最佳实践是**预防性检查**,而非 try-catch 包裹每个 key 操作:
- 每次从
selectedKeys()取出 key 后,先调用if (!key.isValid()) continue; - 确保在处理完一个 key(尤其是发生 I/O 异常或 channel 关闭后)立即调用
key.cancel(),并显式key.channel().close()(如果需要) - 遍历 selectedKeys 时,使用
Iterator并配合iterator.remove(),避免 ConcurrentModificationException,也便于控制 key 生命周期
如果真想 catch,该怎么写?
仅在极少数无法完全规避的场景下(例如封装了不透明的 key 操作工具),可局部捕获:
for (SelectionKey key : selector.selectedKeys()) {
try {
if (!key.isValid()) continue;
// ... 处理 OP_ACCEPT / OP_READ 等
handleKey(key);
} catch (CancelledKeyException e) {
// 日志记录,或做清理(通常 key 已失效,无需额外 cancel)
System.err.println("Key was cancelled during handling: " + key);
}
}
注意:catch (CancelledKeyException) 不应掩盖根本问题——它说明你的 key 管理逻辑存在竞态或生命周期错误。
关键细节别忽略
-
key.isValid()是轻量、线程安全的检查,必须放在所有 key 操作之前 -
key.cancel()是幂等的,重复调用无害;但调用后 key 立即变为无效,不可再用于任何操作 - Selector 的
select()返回后,selectedKeys()中只包含 当前有效且就绪 的 key,但它们可能在你处理中途被其他线程取消
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











