单个selector无法利用多核cpu,必须采用主从reactor模式:boss线程组负责accept并分配连接,worker线程组各持独立selector处理io,且selector与创建线程严格绑定。

在多核CPU环境下,JVM中单个Selector并不能自动利用多个CPU核心——它本质是单线程事件轮询器。要真正发挥多核优势,必须采用“多Selector + 线程分工”的架构设计,而非让一个Selector被多个线程并发调用(这会引发竞态且不被允许)。
多Selector分治模型:主从Reactor模式
这是Netty等高性能框架采用的主流方案,也是JVM下最成熟、可扩展的多核适配架构:
- 主线程(Boss线程组):仅负责Accept新连接,每个Boss线程绑定一个独立Selector,监听ServerSocketChannel;通常线程数 = CPU核心数 × 1(或2),避免过度竞争
- 从线程(Worker线程组):每个Worker线程持有自己的Selector,负责处理已建立连接的读写事件;线程数一般设为 CPU核心数 × 2~3,兼顾IO等待与CPU计算平衡
- 新连接建立后,由Boss线程通过无锁方式(如轮询或负载感知)将其移交给某个Worker线程,并将对应SocketChannel注册到该Worker的Selector上
Selector与线程的严格绑定关系
每个Selector实例必须且只能由创建它的线程调用select()或selectNow()——这是NIO规范强制要求。违反会导致ClosedSelectorException或未定义行为:
- 不能把同一个Selector传给多个线程去轮询
- 不能在线程A中register(),再在线程B中调用select()
- Selector内部依赖底层系统调用(如epoll_wait),其文件描述符表和就绪队列与线程上下文强关联
跨核调度的关键支撑机制
真正实现多核高效协同的,不是Selector本身,而是围绕它构建的协作层:
- 无锁任务队列:Boss向Worker移交Channel时,使用MpscArrayQueue等高性能队列,避免synchronized开销
- 事件就绪通知:Worker线程阻塞在select()时,Boss可通过interrupt()或空写管道(如Linux eventfd)唤醒它,实现低延迟调度
- SelectionKey分离管理:每个Worker只处理自己Selector上的selectedKeys,无需全局同步;keyIterator.remove()也仅作用于本线程本地集合
为什么不用单Selector + 多线程处理selectedKeys?
看似可行,实则不可行:
- selectedKeys()返回的是共享的HashSet,多线程遍历+remove()会触发ConcurrentModificationException
- 即使加锁,也会让高并发下的事件处理变成串行瓶颈,失去多核意义
- Selector内部状态(如wakeup状态、就绪队列)非线程安全,多线程调用select()可能破坏一致性











