Java中Selector实现多路复用的关键是单线程轮询非阻塞通道就绪状态,依赖epoll等系统调用高效通知;必须调configureBlocking(false),仅SelectableChannel子类可注册,FileChannel不可用;流程须闭环:注册→select()→遍历selectedKeys()处理→iterator.remove()清理;attachment用于绑定通道专属上下文,保障线程安全与解耦。

Java 中用 Selector 监听多个通道事件,关键不是“同时监听”,而是让一个线程**按需轮询多个非阻塞通道的就绪状态**。它不靠线程数量堆叠,而是靠操作系统底层(如 Linux 的 epoll)高效通知哪些通道真正准备好了读、写或连接——这才是多路复用的本质。
必须用非阻塞通道注册
只有调过 configureBlocking(false) 的 SelectableChannel 才能注册到 Selector。常见可用类型:
- ServerSocketChannel:只注册 OP_ACCEPT(监听新连接)
- SocketChannel:通常注册 OP_READ;有数据要发时可临时加 OP_WRITE,写完建议移除,避免空轮询
- DatagramChannel:支持 OP_READ / OP_WRITE
- FileChannel 不可注册——它不是 SelectableChannel 子类
漏掉 configureBlocking(false),注册时直接抛 IllegalBlockingModeException,这是新手最常踩的坑。
注册 → select() → 处理 → remove() 四步不能跳
Selector 不是自动运行的“监听器”,整个流程必须手动闭环:
立即学习“Java免费学习笔记(深入)”;
-
注册:用
channel.register(selector, ops, attachment),其中ops是位掩码,比如OP_READ | OP_WRITE -
等待就绪:调
selector.select()(阻塞)或select(500)(超时 500ms) -
处理事件:遍历
selectedKeys(),用Iterator安全访问每个SelectionKey -
清理键:每次处理完一个 key 后,必须调
iterator.remove();否则下次select()还会返回它,导致重复处理
注意:不能在遍历时直接调 selectedKeys().remove(key) 或 key.cancel(),会触发 ConcurrentModificationException。
区分事件类型,按需响应
不同事件对应不同操作逻辑,不能混用:
-
OP_ACCEPT:只用于 ServerSocketChannel。收到后,调
serverChannel.accept()得到新 SocketChannel,设为非阻塞并注册 OP_READ - OP_CONNECT:客户端用,表示非阻塞 connect 已完成,可开始读写
-
OP_READ:调
channel.read(buffer),注意判断返回值是否为 -1(对端关闭)或 0(暂无数据) - OP_WRITE:通常不长期注册。仅当 write() 返回 0(内核缓冲区满)时才加 OP_WRITE;一旦写完,立刻取消该兴趣集,否则 selector 会持续唤醒
用 attachment 持有通道上下文
注册时传入的 attachment(如 ByteBuffer、自定义 Context 对象),可在事件处理中直接获取,避免全局查找或状态错乱:
- 例如:
channel.register(selector, OP_READ, ByteBuffer.allocate(2048)) - 处理时:
ByteBuffer buf = (ByteBuffer) key.attachment();
这样每个通道都有独立缓冲区和业务状态,线程安全又解耦。


















