创建selector需配合非阻塞通道、事件注册和轮询机制;通道须实现selectablechannel并调用configureblocking(false);注册时指定op_accept/op_read/op_write/op_connect;select()后遍历selectedkeys()处理就绪事件并remove(),避免重复触发。

直接调用 Selector.open() 就能创建一个多路复用器,但仅这一步远远不够——它只是起点。真正启用高效多路复用,必须配合非阻塞通道、事件注册和轮询机制,缺一不可。
必须先创建非阻塞通道
Selector 只能管理实现了 SelectableChannel 接口且处于非阻塞模式的通道(如 ServerSocketChannel、SocketChannel、DatagramChannel)。FileChannel 不支持非阻塞,因此不能注册到 Selector。
- 调用
channel.configureBlocking(false)是硬性前提,否则注册会抛出IllegalBlockingModeException - 服务端通常先打开
ServerSocketChannel并绑定端口;客户端则用SocketChannel发起连接 - UDP 场景下使用
DatagramChannel,同样需设为非阻塞
注册时明确指定监听事件类型
通道注册到 Selector 后,必须声明你关心哪些就绪状态,Selector 才会在对应事件发生时通知你。
-
OP_ACCEPT:仅用于ServerSocketChannel,表示有新客户端连接待接受 -
OP_READ:通道有数据可读(包括对端关闭连接,此时read()返回 -1) -
OP_WRITE:通道底层缓冲区有空间可写(多数情况默认就绪,慎用,易导致 busy-loop) -
OP_CONNECT:仅用于客户端SocketChannel,表示 TCP 连接已建立完成
正确轮询并处理就绪键集合
调用 select() 是触发事件分发的关键动作,它会阻塞直到至少一个注册事件就绪(或被唤醒/超时/关闭)。
- 每次调用后,通过
selector.selectedKeys()获取就绪的SelectionKey集合 - 必须在处理完每个 key 后调用
iterator.remove(),否则该 key 会在下次 select 中重复出现 - 用
key.isAcceptable()、key.isReadable()等方法判断具体事件类型,再转型获取对应 channel 处理 - 避免在 select 循环中做耗时操作,否则会阻塞整个 I/O 轮询线程
注意底层机制与常见陷阱
Selector 在 Linux 上基于 epoll 实现(JDK 7+),性能远高于传统 select,但仍有几个关键点容易出错:
- 一个 Selector 实例应由单个线程独占使用,不建议多线程并发调用
select()或修改其 keys - channel 关闭时,对应的 SelectionKey 会自动失效;但主动取消注册要用
key.cancel() - 不要忽略
selectNow()和select(long timeout)的适用场景:前者适合纯非阻塞调度,后者适合控制响应延迟 - wakeup() 可强制唤醒阻塞中的 select,常用于外部线程通知 I/O 线程刷新状态










