NIO vs BIO 对比
10 万并发连接,你怎么选?
有人说「NIO 肯定比 BIO 好」,这话对了一半。脱离场景谈选型,都是耍流氓。
先看效果
BIO 模式:10000 连接 = 10000 线程
┌─────────┐ ┌─────────┐ ┌─────────┐
│Client 1 │ ────────→ │ 线程 1 │ │ │
└─────────┘ └─────────┘ │
┌─────────┐ └─────────┘ │
│Client 2 │ ────────→ │ 线程 2 │ │ 磁 │
└─────────┘ └─────────┘ │ 盘 │
┌─────────┐ ┌─────────┐ │ │
│Client 3 │ ────────→ │ 线程 3 │ │ │
└─────────┘ └─────────┘ └─────────┘NIO 模式:10000 连接 = 1 线程(加几个备用)
┌─────────┐
│Client 1 │ ──┐
└─────────┘ │
┌─────────┐ ├───────────────────────→ ┌─────────┐ ── 1 线程 ──→ │ 磁盘 │
│Client 2 │ ──┤ └─────────┘ └─────────┘
└─────────┘ │
┌─────────┐ │
│Client 3 │ ──┘
└─────────┘核心差异:一张表说清楚
| 对比 | BIO | NIO |
|---|---|---|
| 编程模型 | 面向流,每连接一线程 | 面向 Buffer,多路复用 |
| 阻塞方式 | 同步阻塞 | 非阻塞 / 多路复用 |
| 线程模型 | 1 连接 1 线程 | 多路复用器(1 线程管 N 连接) |
| 适用场景 | 连接数少、低并发 | 高并发 |
| API 复杂度 | 简单直观 | 复杂(Selector、Buffer、SelectionKey) |
| 吞吐量 | 低(线程开销大) | 高(少量线程处理大量连接) |
代码对比:同一个功能,两种写法
BIO 服务器
java
// 每个连接一个线程
ServerSocket server = new ServerSocket(8080);
while (true) {
Socket socket = server.accept(); // 阻塞在这里
new Thread(() -> {
try {
BufferedReader reader = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
// 处理数据
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}简单、直观、容易理解。但连接数一多,线程数也跟着爆。
NIO 服务器
java
// 单线程管理所有连接
Selector selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.socket().bind(new InetSocketAddress(8080));
serverChannel.configureBlocking(false);
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select(); // 阻塞
for (SelectionKey key : selector.selectedKeys()) {
if (key.isAcceptable()) {
SocketChannel client = serverChannel.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
}
if (key.isReadable()) {
// 处理数据
}
}
selector.selectedKeys().clear();
}逻辑复杂,但资源利用率高。
性能对比:数字说话
| 指标 | BIO | NIO |
|---|---|---|
| 100 连接 | 100 线程 | 1 线程 |
| 1000 连接 | 1000 线程(内存压力大) | 1 线程 |
| 10000 连接 | 崩溃风险 | 1 线程 |
| 线程切换开销 | 高 | 低 |
| 内存占用 | 高(每线程约 1MB 栈空间) | 低 |
| 编码难度 | 简单 | 复杂 |
选型建议:场景决定方案
| 场景 | 推荐 | 原因 |
|---|---|---|
| 连接数 < 100 | BIO | 简单够用,线程开销可控 |
| 连接数 > 1000 | NIO 或 Netty | 线程资源有限,必须复用 |
| 需要快速开发 | BIO | 学习曲线低,上手快 |
| 长连接场景 | NIO 或 Netty | BIO 线程占用太高 |
| 低延迟敏感 | BIO | NIO 多了系统调用层,延迟略高 |
| 生产环境高性能 | Netty | 成熟稳定,生态完善 |
一句话总结:连接少用 BIO,图省事;连接多用 NIO,省资源。
Netty:站在 NIO 肩膀上
直接用 NIO 写生产代码很累。Netty 封装了这些复杂性:
java
// Netty 风格的服务器
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new EchoServerHandler());
}
});
ChannelFuture f = b.bind(8080).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}Netty 帮你处理了粘包、心跳、流量控制、线程池等问题。如果你要写高性能网络服务,直接用 Netty,别自己造轮子。
选型口诀:
小项目用 BIO 省心,大项目用 Netty 安心,中间地带可以考虑 NIO。
下一节,我们来看几个 NIO 的实战案例。
