Java NIO多线程Reactor采用主从结构:主线程专责accept并轮询分配连接至子Reactor,各子线程持独立Selector处理读写;业务逻辑应交由单独线程池执行以避免阻塞。

Java 中在 NIO 服务端实现多线程 Reactor 模型,核心是把事件分发(Reactor)和事件处理(Handler)分离,并让多个线程并行处理 I/O 事件或业务逻辑。标准的单 Reactor 单线程模型(如 Selector + SelectionKey)容易成为瓶颈,而多线程 Reactor 通常采用「主从 Reactor」(Main-Sub Reactor)结构:主线程专责 accept 连接,多个子线程各自持有独立的 Selector 负责读写事件,避免锁竞争。
主从 Reactor 结构设计
这是最常用且推荐的多线程 Reactor 实现方式:
-
主线程(Main Reactor):只运行一个
Selector,监听 ServerSocketChannel 的OP_ACCEPT事件;一旦有新连接,不直接处理,而是将该SocketChannel注册到某个子 Reactor 线程的Selector上。 -
子线程池(Sub Reactors):每个子线程拥有自己的
Selector和事件循环,负责注册进来的客户端通道的OP_READ/OP_WRITE;可使用轮询(Round-Robin)或负载均衡策略分配连接。 - 业务解耦(可选但推荐):I/O 事件(如 decode、encode)仍在 Sub Reactor 线程执行,但耗时业务逻辑(DB 查询、RPC 调用等)应提交到独立的业务线程池,防止阻塞 I/O 线程。
关键代码结构示意
以下为简化版主从 Reactor 核心骨架(不含异常处理与资源释放):
// 1. 创建多个 Sub Reactor 线程(每个含独立 Selector)
List<subreactor> subReactors = new ArrayList();
for (int i = 0; i <p><font color="gray">// 2. Main Reactor 启动(仅 accept)</font></p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill6639" title="React Router Code Review"><img
src="https://img.php.cn/upload/skill/000/000/081/179103432042105.jpg" alt="React Router Code Review" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill6639" title="React Router Code Review" class="overflowclass">React Router Code Review</a>
<p class="overflowclass">审查 React Router 代码,确保数据加载、变更、错误处理和导航模式符合规范,适用于 React Router v6.4+ 代码、加载器及其他特性。</p>
</div>
<a rel="nofollow" href="/xiazai/skill6639" title="React Router Code Review" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
<pre class="brush:java;toolbar:false;">ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
ssc.bind(new InetSocketAddress(port));
Selector mainSelector = Selector.open();
ssc.register(mainSelector, SelectionKey.OP_ACCEPT);
<p>new Thread(() -> {
while (!Thread.interrupted()) {
try {
mainSelector.select();
Set<selectionkey> keys = mainSelector.selectedKeys();
Iterator<selectionkey> it = keys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
it.remove();
if (key.isAcceptable()) {
SocketChannel ch = ssc.accept();
ch.configureBlocking(false);
// 将 channel 注册到某个 Sub Reactor 的 Selector
chooseSub().registerChannel(ch);
}
}
} catch (IOException e) { /<em> handle </em>/ }
}
}).start();
</selectionkey></selectionkey></p>
// 3. SubReactor 示例(每个线程一个)
class SubReactor implements Runnable {
final Selector selector;
final Thread thread;
<pre class="brush:php;toolbar:false;">SubReactor() throws IOException {
this.selector = Selector.open();
this.thread = new Thread(this);
this.thread.start();
}
void registerChannel(SocketChannel ch) throws IOException {
// 注意:必须通过 selector.wakeup() 唤醒阻塞中的 select()
// 并在 select 循环外注册,避免 ConcurrentModificationException
selector.wakeup();
ch.register(selector, SelectionKey.OP_READ, new ByteBufferAttachment());
}
@Override
public void run() {
while (!Thread.interrupted()) {
try {
selector.select(); // 阻塞等待事件
Iterator<selectionkey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
it.remove();
if (key.isReadable()) {
// 读取数据(非阻塞)
ByteBuffer buf = (ByteBuffer) key.attachment();
SocketChannel ch = (SocketChannel) key.channel();
int n = ch.read(buf);
if (n > 0) {
buf.flip();
// 解析协议(如简单字符串)
byte[] data = new byte[buf.remaining()];
buf.get(data);
String msg = new String(data);
// 提交业务逻辑到业务线程池
businessExecutor.submit(() -> process(msg, ch));
buf.clear();
}
}
}
} catch (IOException e) { /* handle */ }
}
}</selectionkey>}
注意事项与常见陷阱
实际落地时需特别注意以下几点:
-
Selector 线程安全:一个
Selector只能由一个线程调用select(),所有注册/取消注册操作都应在该线程内完成;跨线程注册必须先wakeup()再在下一次循环中执行。 -
附件对象(Attachment)线程安全:
key.attachment()是每个 key 独立的,但若 attachment 是共享对象(如ByteBuffer),需确保无并发读写;推荐每个连接绑定专属 buffer 或使用ThreadLocal<bytebuffer></bytebuffer>。 -
OP_WRITE 的触发时机:不要长期注册
OP_WRITE,它几乎总是就绪,易导致 CPU 100%;只在 write 返回 0(内核发送缓冲区满)时临时注册,写完立即取消。 - 关闭资源顺序:先 cancel key,再 close channel,最后 close selector;channel.close() 会自动 cancel 对应 key,但仍建议显式 cancel 避免残留。
对比 Netty 的启示
Netty 的 NioEventLoopGroup(bossGroup + workerGroup)就是主从 Reactor 的成熟实现:
-
bossGroup对应 Main Reactor,线程数默认为 1; -
workerGroup对应 Sub Reactors,线程数默认为2 × CPU 核数; - 每个
NioEventLoop绑定一个线程 + 一个Selector,任务队列支持异步提交(execute()); - ChannelPipeline 中可自由添加
@SharableHandler 或独占 Handler,天然支持 I/O 与业务线程分离。
如果你不是为了学习原理,生产环境强烈建议直接使用 Netty —— 它已解决粘包拆包、零拷贝、内存池、线程模型切换(NIO / EPOLL / KQUEUE)、SSL 集成等全部细节。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










