Selector线程安全但其键集合非线程安全;多线程并发调用select()和修改interestOps易致状态不一致或ConcurrentModificationException;应统一在select线程内批量处理interestOps变更以避免竞争。

Selector 本身是线程安全的,但它的键集合(selectedKeys、keys、cancelledKeys)不是。多线程并发调用 select() 和修改键的 interestOps(如 key.interestOps(SelectionKey.OP_READ))时,若缺乏协调,极易引发状态不一致、ConcurrentModificationException 或事件丢失。
selector.select() 的同步行为与阻塞风险
select() 方法内部会对 Selector 实例、已注册键集合、已选择键集合依次加锁。这意味着:
- 多个线程同时调用 select() 不会破坏内部状态,但后进入的线程会被阻塞,直到前一个 select 完成
- 如果某线程正在执行 select,另一线程调用 close(),该 close 也会被阻塞,直到 select 结束或 select 线程被唤醒
- select 返回后,
selectedKeys()返回的 Set 是直接引用内部可变集合,**不可在遍历过程中被其他线程修改**
key.interestOps() 修改需显式同步
interestOps 是 SelectionKey 的可变字段,其修改操作(如取消读、添加写)不自动同步。常见危险场景:
- 主线程在 select 循环中遍历 selectedKeys 并调用
key.interestOps(op),而工作线程同时调用同一 key 的interestOps()→ 可能导致事件监听状态错乱 - 一个线程刚调用
key.cancel(),另一线程仍试图通过该 key 操作通道 → 报CancelledKeyException - 未同步地在不同线程中反复切换 OP_READ / OP_WRITE,可能使 select() 下次遗漏就绪通知
安全修改 interestOps 的推荐方式
避免直接跨线程操作 key,而是采用“通知-延迟更新”模式:
- 工作线程不直接调用
key.interestOps(),而是将变更请求(如 “请为 key X 启用 OP_WRITE”)投递到 selector 所在线程的安全队列(如 ConcurrentLinkedQueue) - select 循环中,在每次
select()返回后、处理 selectedKeys 前,先批量消费该队列并调用key.interestOps(newOps) - 所有对 key 的 cancel() 或 interestOps() 调用,统一发生在 select 线程内,彻底规避竞争
为什么不能只靠 synchronized(selector)?
虽然 select() 内部按固定顺序(selector → keys → selectedKeys)加锁,但如果你手动在外部写 synchronized(selector) { key.interestOps(...) },必须严格遵循相同顺序,否则有死锁风险。更稳妥的做法是:让所有键状态变更都收敛到 select 线程执行,不依赖手动锁顺序。

















