能,但依赖操作系统级i/o多路复用(epoll/kqueue)而非并发;关键在注册、分发、清理闭环,所有通道须设非阻塞,事件注册须按类型严格区分,select后必须用迭代器清理selectedkeys,业务逻辑不可阻塞事件线程。

能,但不是靠“并发”,而是靠操作系统级 I/O 多路复用(Linux epoll / macOS kqueue)把就绪事件推给 Java 层。关键不在轮子多快,而在注册、分发、清理三步是否闭环。
所有通道必须提前设为非阻塞
这是硬性前提,漏掉直接抛 IllegalBlockingModeException:
-
ServerSocketChannel 创建后、注册前必须调
configureBlocking(false),否则register(selector, OP_ACCEPT)失败 - 每个 accept() 得到的 SocketChannel,必须立刻设非阻塞,再注册
OP_READ;顺序不能颠倒 - 一旦 channel 关闭,再调
configureBlocking()会抛ClosedChannelException
事件注册要严格按通道类型区分
混用会导致 key 失效、select() 返回 0、服务静默卡死:
- ServerSocketChannel 只注册 OP_ACCEPT:用于监听新连接,不支持 OP_READ/OP_WRITE
- SocketChannel 注册 OP_READ:新连接建立后立即注册,等待数据到达
-
OP_WRITE 要按需临时注册:仅当
write()返回值小于待写字节数(写半包)时追加;写完立刻取消:key.interestOps(key.interestOps() & ~OP_WRITE)
每次 select() 后必须用迭代器清理 selectedKeys
selectedKeys() 是复用集合,不手动移除已处理 key,下一轮还会重复触发:
- 必须用
Iterator<selectionkey> it = selector.selectedKeys().iterator()</selectionkey> - 循环中调
it.next()处理,然后立刻it.remove() - 禁止在遍历时调
selectedKeys().remove(key)—— 抛ConcurrentModificationException - 禁止 for-each 后清空整个集合 —— 会丢掉未处理的 key
避免业务逻辑阻塞事件线程
Selector 线程只做字节收发和状态跳转,重活必须卸载:
- 不要在
OP_READ回调里做 JSON 解析、DB 查询、HTTP 调用 - 收到数据后,把
ByteBuffer或解码结果交给业务线程池(如ForkJoinPool.commonPool()) - 响应数据写入缓存 buffer,改注册
OP_WRITE,等下轮 select() 通知再发 - 每条连接的
ByteBuffer建议池化(如 Netty PooledByteBufAllocator),避免 DirectMemory 爆炸或 GC 雪崩
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











