核心是用selector多路复用+非阻塞通道+事件分发模型:serversocketchannel仅注册op_accept,socketchannel设为非阻塞并按需注册op_read/op_write;select后必须遍历并remove selectedkeys;读写用堆外bytebuffer复用;耗时逻辑交由业务线程池;正确管理selectionkey生命周期与附件。

核心是用好 Selector 多路复用 + 非阻塞通道 + 合理的事件分发模型,避免线程膨胀和事件堆积。
用单个 Selector 管理成千上万个连接
ServerSocketChannel 只注册 OP_ACCEPT,专门接收新连接;每个接入的 SocketChannel 必须设为非阻塞,并只注册 OP_READ(写操作按需临时注册 OP_WRITE);一个 Selector 能稳定支撑数万连接,但要注意不要在循环中反复调用 selector.select() 时不清理 selectedKeys。
- 每次
select()返回后,必须遍历selectedKeys()并对每个 key 调用iterator.remove() - 不清理会导致同一事件被重复处理,引发数据错乱或 CPU 打满
- 建议用
for (Iterator<selectionkey> it = selector.selectedKeys().iterator(); it.hasNext(); )</selectionkey>方式安全遍历
读写操作必须非阻塞,且缓冲区要复用
所有 SocketChannel 必须调用 configureBlocking(false);读写都基于 ByteBuffer,避免每次分配新 buffer——用 ByteBuffer.allocateDirect(4096) 创建堆外缓冲区,配合对象池或 ThreadLocal 缓存复用。
- 堆外内存减少 GC 压力,适合高频小包场景
- 读操作:buffer 先
clear(),再channel.read(buffer),然后flip()解析 - 写操作:写完记得
buffer.compact()或重置 position/limit,避免下次读写错位
耗时逻辑必须剥离到业务线程池
Selector 线程只做 I/O 事件分发,不能执行数据库查询、JSON 解析、文件写入等操作;收到完整请求后,把解码后的数据封装成任务,提交给独立的业务线程池处理。
- 推荐使用有界队列的
ThreadPoolExecutor,防止突发流量压垮内存 - 响应结果通过
key.interestOps(OP_WRITE)重新注册写事件,由 Selector 线程触发发送 - 若一次写不完(
write()返回值小于 buffer 剩余),保持 OP_WRITE 注册,等下次就绪再续写
注意 SelectionKey 的生命周期管理
不要在处理过程中随意调用 key.cancel(),尤其不能在 isWritable() 块里直接取消;连接关闭应统一走 channel.close() + key.cancel() + key.attach(null) 清理附件。
- 取消 key 后该 channel 就不再受 Selector 监控,无法再收发数据
- 异常断连时,确保在
catch块中完成 channel 关闭和 key 清理,避免资源泄漏 - 可利用
key.attach(Object)绑定会话上下文(如用户 ID、解码器),提升状态管理效率
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











