Java 怎么在 NIO 服务端实现多线程 Reactor 线程模型

小磊姑娘_7541

小磊姑娘_7541

2026-09-04

708人浏览

原创

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

java 怎么在 nio 服务端实现多线程 reactor 线程模型

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 中可自由添加 @Sharable Handler 或独占 Handler,天然支持 I/O 与业务线程分离。

如果你不是为了学习原理,生产环境强烈建议直接使用 Netty —— 它已解决粘包拆包、零拷贝、内存池、线程模型切换(NIO / EPOLL / KQUEUE)、SSL 集成等全部细节。

Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

2023.06.15

9577

6

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

2023.07.05

6742

9

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

2023.07.31

5972

8

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.08.01

1044

3

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.08.02

868

3

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.02

1256

5

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.02

2509

5

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

2023.08.03

19851

3

配置java环境变量
配置java环境变量

配置Java环境变量是为了让操作系统能够识别和使用Java的相关命令和功能。本专题为大家提供配置java环境变量相关文章,帮助大家解决问题。

2023.08.03

1135

8

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
dev.java 官方:Learn Java
dev.java 官方:Learn Java

共0课时 | 0人学习

Java JDBC数据库连接官方教程
Java JDBC数据库连接官方教程

共0课时 | 0人学习