channel.register(selector, OP_READ) 后没反应,最常见原因是未在注册前调用 configureBlocking(false),导致抛出 IllegalBlockingModeException;此外,刚注册时若无数据到达,select() 不会唤醒,read() 返回 0 属正常,仅当返回 -1(对端关闭)才需 key.cancel() 并关闭连接。

能,但必须严格遵循注册→轮询→分发三步闭环,漏掉任一环就会丢事件、卡连接、泄漏资源。
为什么 channel.register(selector, OP_READ) 后没反应?
最常见原因是 Channel 没设为非阻塞模式——configureBlocking(false) 必须在 register() 之前调用,否则抛 IllegalBlockingModeException。另外,刚注册的 SocketChannel 可能还没收到数据,select() 不会立即唤醒;它只在真正有数据到达时才触发 OP_READ 就绪。
- ServerSocketChannel 注册时只应关注
OP_ACCEPT,收到新连接后再创建 SocketChannel、设非阻塞、再注册OP_READ - 不要在注册后立刻尝试
read():此时缓冲区为空,read()返回 0,不是错误,但也不代表就绪 - 检查操作系统限制:Linux 默认
epoll没上限,但若用旧版 JDK 或误配,可能受ulimit -n影响
select() 返回后不 key.remove() 会怎样?
已处理的 SelectionKey 会滞留在 selectedKeys() 集合中,下次 select() 仍会返回它,导致重复处理、逻辑错乱甚至无限循环。Java 的 selectedKeys() 是复用集合,不是每次新建。
- 必须在遍历中用
Iterator.remove(),不能用selectedKeys().remove(key)(会抛UnsupportedOperationException) - 推荐写法:
Iteratoriter = selector.selectedKeys().iterator(); while (iter.hasNext()) { SelectionKey k = iter.next(); iter.remove(); /* 处理k */ } - 如果处理过程中发生异常,也要确保
iter.remove()被执行,否则该 key 会一直残留
read() 返回 -1 或 0 怎么区分和处理?
read() 返回 -1 表示对端已关闭连接(EOF),必须立即 key.cancel() 并 channel.close();返回 0 是合法行为,表示本次无新数据可读(比如 TCP 窗口暂满或数据未到),不是错误,不应关闭连接。
- 仅当
read() == -1时调用key.cancel(),之后可安全丢弃该 key 和 channel - 不要在
OP_READ处理里直接write():socket 发送缓冲区可能满,导致阻塞整个事件线程;应改用key.interestOps(OP_WRITE),等下一轮select()通知可写再发 - 使用
attachment存ByteBuffer,避免每次 new 缓冲区:channel.register(selector, OP_READ, ByteBuffer.allocate(4096))
业务逻辑卡住 Selector 线程怎么办?
在 OP_READ 或 OP_WRITE 回调里做 JSON 解析、DB 查询、HTTP 调用等操作,会拖慢整个轮询节奏,其他连接饿死。Selector 线程只负责字节收发与状态跳转,不干重活。
- 耗时操作必须交给线程池,例如:
ForkJoinPool.commonPool().submit(() -> { /* 解析/查库 */ }) - 响应数据生成后,通过
CompletableFuture或回调通知 Selector 线程准备写入 - 缓冲区操作要严谨:
buffer.flip()后读、buffer.clear()后写,漏掉flip()会导致读不到数据,漏掉clear()会导致后续写入失败
真正难的不是让 Selector 跑起来,而是每个 buffer.clear()、每次 key.remove()、每处阻塞调用的剥离——少一个,连接就可能卡住;多一次同步等待,千个连接就一起抖动。










