发布于2026-07-10 阅读(0)
扫一扫,手机访问
先抛几个关键判断:要实现单线程管理十万级并发连接,核心在于理解 Selector 的工作机制——它不是什么黑科技,而是现代操作系统提供的一项底层能力。

它不靠线程遍历,而是依赖操作系统内核的 I/O 多路复用机制(Linux 上是 epoll,Windows 上是 IOCP 封装)。Selector.select() 调用会阻塞,直到至少一个注册的 Channel 就绪(比如有数据可读、可写、连接完成),这时内核把就绪列表返回给 JVM,Selector.selectedKeys() 返回的就是这批活跃事件的集合——你只处理真正需要响应的连接,而不是每毫秒扫一遍十万条连接。
关键点在于:就绪通知由内核触发,不是 Ja va 主动轮询;一次 select() 可能唤醒多个 Channel,但你只迭代 selectedKeys() 里那几十或几百个,复杂度从 O(N) 降到 O(1)~O(K),K 是就绪数。
SocketChannel 默认是阻塞的,直接注册到 Selector 会抛 IllegalBlockingModeException。而且,不指定 interestSet 就等于没注册任何事件,这个 Channel 永远不会出现在 selectedKeys() 中。
channel.configureBlocking(false)channel.register(selector, SelectionKey.OP_READ) 注册,不能漏掉第二个参数OP_READ 事件里直接 channel.write()——可能写不完,得改用 key.interestOps(SelectionKey.OP_WRITE) 并确保 OP_WRITE 可用(注意:epoll 中 OP_WRITE 就绪条件很宽松,几乎总是就绪,慎用)Selector 内部用一个 SelectedSelectionKeySet 缓存就绪 key,每次 select() 返回前会把新就绪的 key 加进去,但不会自动清理已处理的 key。如果你只遍历不 remove(),同一个 key 会在下一轮 select() 后再次出现,导致重复处理甚至死循环。
正确做法是用迭代器边遍历边删:
Iteratoriter = selector.selectedKeys().iterator();while (iter.hasNext()) { SelectionKey key = iter.next(); iter.remove(); // 必须这一步 if (key.isReadable()) handleRead(key); if (key.isWritable()) handleWrite(key);}
单个 Selector 在 Linux 上用 epoll 实现,理论支持百万级连接,但实际卡在三个地方:JVM 堆内存(每个 SelectionKey 约 100+ 字节)、GC 压力(频繁创建/销毁 key)、以及单线程处理逻辑的 CPU 占用。当业务逻辑稍重(比如 JSON 解析、DB 查询),单线程立刻成为瓶颈,连接数上不去。
select() 循环里做耗时操作;IO 事件只做 read/write,业务逻辑扔给线程池ByteBuffer 池(如 Netty 的 PooledByteBufAllocator)减少 GCThread,但可以按需分发到固定线程池——这不是违背单线程 IO 模型,而是 IO 线程只管调度,不干重活Selector.wakeup() 频繁调用会导致 select() 提前返回,增加空转,仅在外部线程需中断阻塞时谨慎使用真正难的不是撑住十万连接,而是让这十万连接持续低延迟收发——大部分坑藏在业务逻辑的同步阻塞和堆外内存管理里,而不是 Selector 本身。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8