IO多路复用

什么是io多路复用?
 

IO 多路复用(I/O Multiplexing) 是面试中的高频问题,也是 Redis、Nginx、Netty、Tomcat 等高性能服务器的核心技术。

一句话理解:

一个线程同时监听多个 Socket,当某个 Socket 有数据可读/可写时,再去处理它,而不是一个连接对应一个线程。


一、为什么需要 IO 多路复用?

假设你要开发一个聊天服务器。

方案一:一个连接一个线程(BIO)

 
客户端A ───── Thread1
客户端B ───── Thread2
客户端C ───── Thread3
...
客户端10000 ─ Thread10000
 

Java 代码:

 
while (true) {
Socket socket = serverSocket.accept();

new Thread(() -> {
while (true) {
socket.getInputStream().read();
}
}).start();
}
 

存在的问题

假设:

 
10000 个连接

真正发消息的只有 100 个
9900 个线程都在等待
 

这些线程:

 
read()
 

一直阻塞。

那么:

  • 每个线程都需要栈空间(默认约 1MB)
  • 线程切换(Context Switch)开销大
  • CPU 大量时间浪费在线程调度上

所以:

 
连接越来越多

线程越来越多

CPU 被线程切换拖死
 

二、IO 多路复用是如何解决的?

它的思想非常简单:

不要一个线程等一个 Socket,而是一个线程管理很多 Socket。

例如:

 
线程


├── socket1
├── socket2
├── socket3
├── socket4
├── socket5
...
├── socket10000
 

线程不再:

 
socket.read()
 

而是:

 
select()

或者

epoll_wait()
 

内核会告诉你:

 
哪些 socket 有数据
 

例如:

 
10000 个连接

只有:

socket3
socket88
socket999

有数据
 

线程只处理:

 
socket3.read()

socket88.read()

socket999.read()
 

其他连接不用管。


三、工作流程

例如:

 
客户端A
客户端B
客户端C
 

服务器:

 
                Thread



select()/epoll_wait()



───────────────────────────────────
SocketA SocketB SocketC
 

假设:

 
A没有数据

B有数据

C没有数据
 

内核返回:

 
SocketB 可读
 

线程:

 
read(SocketB)
 

处理完成:

 
继续 select()
 

四、Linux 的三种 IO 多路复用

1. select

最早出现。

流程:

 
用户



select()



内核检查所有socket



返回可读socket
 

问题:

(1) 每次都遍历所有 Socket

例如:

 
10000 个 socket
 

每次:

 
for(socket){

检查有没有数据

}
 

即使只有一个 Socket 有数据。

时间复杂度:

 
O(n)
 

(2) 有最大连接数限制

Linux:

 
1024
 

Windows:

 
64
 

2. poll

和 select 基本一样。

区别:

没有:

 
1024
 

限制。

但是:

 
仍然遍历全部 Socket
 

时间复杂度:

 
O(n)
 

3. epoll(Linux)

现在互联网几乎都用它。

Redis

Nginx

Netty

Tomcat

RocketMQ

Kafka

基本都基于 epoll。

工作方式

第一次:

 
epoll_create()
 

创建:

 
红黑树
 

保存所有 Socket。

然后:

 
epoll_ctl()
 

注册:

 
socket
 

以后:

 
epoll_wait()
 

线程睡眠。

如果:

 
socket123

收到数据
 

内核主动放到:

 
Ready List
 

线程醒来:

 
直接处理

socket123
 

不用遍历。

时间复杂度:

 
O(1)
 

(更准确地说,返回就绪事件的成本与就绪事件数量相关,而不是与监听的 Socket 总数线性相关。)


五、epoll 为什么快?

例如:

 
100000 个连接
 

只有:

 
20 个连接

有数据
 

select

 
100000



全部遍历



找到20个
 

epoll

 
内核已经知道:

20个有数据



直接返回20个
 

所以:

 
select

100000 次检查



epoll

20 次处理
 

六、Java 中如何使用?

Java NIO:

 
Selector selector = Selector.open();

ServerSocketChannel server = ServerSocketChannel.open();

server.configureBlocking(false);

server.register(selector, SelectionKey.OP_ACCEPT);

while (true) {

selector.select();

Set<SelectionKey> keys = selector.selectedKeys();

for (SelectionKey key : keys) {

if (key.isAcceptable()) {

}

if (key.isReadable()) {

}
}
}
 

这里:

 
Selector
 

就是 Java 对 IO 多路复用的封装。

Linux 下:

 
Selector



epoll
 

Windows:

 


select
 

七、Redis 为什么是单线程还能支持十几万连接?

Redis 不是一个线程处理一个连接。

它是:

 
一个线程



epoll



监听几万个 socket



哪个socket有数据



处理哪个socket
 

例如:

 
100000 个连接



99950 个空闲



50 个请求



Redis 只处理50个
 

所以:

 
CPU 利用率很高
 

八、Netty 为什么性能高?

Netty 的 EventLoop 本质上就是:

 
while(true){

selector.select();

收到事件

处理事件

}
 

一个 EventLoop 可以管理:

 
几千

甚至

几万个连接
 

这就是 Netty 能支撑高并发网络通信的关键原因之一。


九、BIO、NIO、AIO 的区别

模型是否阻塞一个线程处理并发能力典型应用
BIO 一个连接 传统 Java Socket
NIO(IO 多路复用) 非阻塞 多个连接 Redis、Netty、Nginx
AIO 异步非阻塞 多个连接 理论更高 Java 中实际使用较少

十、面试中的标准回答(2 分钟版)

IO 多路复用是一种让一个线程同时监听多个 I/O 连接的技术。它通过 selectpollepoll 等机制,将多个 Socket 注册到内核,由内核负责监听这些 Socket 的事件。当某个 Socket 可读、可写或发生其他事件时,内核通知应用程序,应用程序只处理这些就绪的连接,而不是轮询所有连接。

在 Linux 中,生产环境几乎都使用 epoll。相比 selectpollepoll 不需要每次遍历所有 Socket,而是维护就绪事件列表,只返回发生事件的连接,因此在大量连接、少量活跃连接的场景下性能非常高。

Java 的 NIO Selector 底层在 Linux 上就是基于 epoll 实现的,因此像 RedisNettyNginxKafkaRocketMQ 等高性能网络框架,都大量使用了 IO 多路复用技术来支持海量并发连接。

posted on 2026-07-06 16:55  日思日睿  阅读(4)  评论(0)    收藏  举报