Skip to content

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 │ ──┘
└─────────┘

核心差异:一张表说清楚

对比BIONIO
编程模型面向流,每连接一线程面向 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();
}

逻辑复杂,但资源利用率高。

性能对比:数字说话

指标BIONIO
100 连接100 线程1 线程
1000 连接1000 线程(内存压力大)1 线程
10000 连接崩溃风险1 线程
线程切换开销
内存占用高(每线程约 1MB 栈空间)
编码难度简单复杂

选型建议:场景决定方案

场景推荐原因
连接数 < 100BIO简单够用,线程开销可控
连接数 > 1000NIO 或 Netty线程资源有限,必须复用
需要快速开发BIO学习曲线低,上手快
长连接场景NIO 或 NettyBIO 线程占用太高
低延迟敏感BIONIO 多了系统调用层,延迟略高
生产环境高性能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&lt;SocketChannel&gt;() {
         @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 的实战案例。

基于 VitePress 构建