selector 是 java 对 epoll/kqueue 的封装,单线程轮询非阻塞通道实现高吞吐;必须全通道 configureblocking(false);事件注册须严格匹配类型(如 serversocketchannel 仅 op_accept);select 后须用 iterator.remove() 清理 selectedkeys;推荐 select(timeoutms) 避免空转或阻塞。

靠操作系统内核的 I/O 多路复用机制(如 Linux 的 epoll、macOS/BSD 的 kqueue),Selector 本质是 Java 对这些系统调用的封装。它不真正“并发”处理连接,而是让单个线程高效轮询成千上万个已注册的非阻塞通道,只在有事件就绪时才唤醒并处理——这才是高吞吐、低资源占用的关键。
必须确保所有通道是非阻塞模式
这是硬性前提,漏掉会直接抛 IllegalBlockingModeException:
-
ServerSocketChannel 在注册前必须调
configureBlocking(false) - 每个新 accept 出来的 SocketChannel 也要立刻设为非阻塞,再注册到 Selector
- 不能等注册完再设置;顺序错误 = 注册失败或运行时报错
事件注册要严格区分通道类型
不同通道支持的事件不同,混用会导致 key 失效、逻辑崩溃:
- ServerSocketChannel 只注册 OP_ACCEPT:用于监听新连接请求
- SocketChannel 注册 OP_READ(通常):数据到达时触发读操作
- OP_WRITE 要按需临时注册:比如写半包后缓冲区满,需等待可写才注册;写完立即取消,避免持续就绪引发忙等
- 绝对不要给 ServerSocketChannel 注 OP_READ,也不给刚建立的 SocketChannel 直接注册 OP_ACCEPT
select() 后必须正确遍历和清理 selectedKeys
每次 select() 返回的是就绪事件快照,不清理就会重复处理甚至阻塞后续轮询:
- 用
Iterator<selectionkey></selectionkey>遍历selector.selectedKeys() - 处理完每个 key 后,**必须调
iterator.remove()**(不是集合的 remove) - 不能在遍历时直接调
selectedKeys().remove(key),会抛ConcurrentModificationException - 不要用 for-each 循环后清空整个集合(如
keys.clear()),会丢失未处理 key
合理控制 select() 的阻塞行为
避免无限等待或空转消耗 CPU:
- 默认
select()会一直阻塞,直到至少一个通道就绪 - 生产环境建议用带超时的
select(timeoutMs),例如select(100),兼顾响应性和效率 - 需要外部线程唤醒时(如关闭信号、定时任务),调
selector.wakeup(),它会立即中断阻塞中的 select() - wakeup() 是线程安全的,但频繁调用会影响性能,应有节制











