关于多线程取消SelectionKey后处理selectedKeys是否会抛出CanceledKeyException的疑问
多线程取消SelectionKey后,处理selectedKeys是否会抛出CanceledKeyException?
嘿,咱们来聊聊你遇到的这个NIO多线程问题~先直接给你明确答案,再慢慢拆解细节:
核心结论
- 当另一个线程调用
clientKey.cancel()并唤醒selector后,selectedKeys集合里确实可能包含已被取消的key; - 对已取消的key调用
isConnectable()、isWritable()这类方法,一定会抛出CanceledKeyException。
为什么会这样?
SelectionKey的cancel()方法只是把这个键标记为「已取消」,并不会立刻从selector的键集合里移除。selector只会在以下几种时机清理已取消的键:
- 下一次调用
select()、selectNow()或select(long)时; - 调用
selector.keys()或selector.selectedKeys()时(部分JDK实现会在这时候做清理,但不能完全依赖)。
所以当你的selector被wakeup()唤醒后,selectedKeys里可能还残留着那个已经被标记为取消的key,这时候去访问它的状态就会触发异常。
怎么解决?
最稳妥的做法是在处理每个selectedKey之前,先检查它的有效性,也就是调用key.isValid()。如果返回false,直接跳过这个key的处理即可。
给你调整一下代码示例,把关键检查加上:
while (clientKey.isValid()) { final var count = selector.select(0L); assert count >= 0; // why not 1? for (final var i = selector.selectedKeys().iterator(); i.hasNext(); ) { final var key = i.next(); i.remove(); // 务必记得移除,否则下次select还会重复拿到这个key // 先检查key是否有效,避免已取消的key触发异常 if (!key.isValid()) { continue; } final var channel = key.channel(); assert channel == client; if (key.isConnectable()) { // finish connect } else if (key.isWritable()) { // write } } }
另外还要注意:你循环条件里的clientKey.isValid()存在竞态风险——比如刚判断完为true,另一个线程就调用了cancel(),所以循环内部的key.isValid()检查是必不可少的,不能只依赖外层的循环条件。
备注:内容来源于stack exchange,提问作者Jin Kwon
相关产品推荐
相关产品推荐

