Skip to content

阻塞 IO vs 非阻塞 IO

read() 方法会「卡住」多久?这是阻塞和非阻塞的核心区别。

阻塞 IO(BIO)

调用 read() 时,如果没有数据,线程就一直等待,直到:

  • 数据来了,返回读到的字节数
  • 流关闭了,返回 -1
  • 出错了,抛出异常
java
// BIO 模式:没数据就一直等
ServerSocket server = new ServerSocket(8080);
Socket socket = server.accept(); // 阻塞:等客户端连接

InputStream in = socket.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf); // 阻塞:等客户端发数据

BIO 的问题是:一个线程只能处理一个连接。10000 个连接就需要 10000 个线程,线程切换开销巨大。

非阻塞 IO(NIO)

调用 read() 时,如果没有数据,立刻返回 -1 或 0,不会等待。

java
// NIO 模式:非阻塞
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false); // 设置为非阻塞模式

ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = channel.read(buffer); // 立即返回,没数据返回 0 或 -1

但光非阻塞没用——你还是得不停地轮询「有没有数据」,这叫忙等(busy wait),浪费 CPU。

多路复用:非阻塞的真正价值

光非阻塞不够,还需要多路复用器(Selector)来监听多个 Channel,只在有事件时才真正去读写。

java
// 多路复用:用 Selector 在一个线程里监听多个连接
Selector selector = Selector.open();

ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(8080));
serverChannel.configureBlocking(false);
serverChannel.register(selector, SelectionKey.OP_ACCEPT); // 监听 accept 事件

SocketChannel clientChannel = SocketChannel.open();
clientChannel.configureBlocking(false);
clientChannel.register(selector, SelectionKey.OP_READ); // 监听 read 事件

// 阻塞在这里,等任意一个 Channel 有事件就返回
while (selector.select() > 0) {
    for (SelectionKey key : selector.selectedKeys()) {
        if (key.isAcceptable()) {
            // 有新连接
            ServerSocketChannel server = (ServerSocketChannel) key.channel();
            SocketChannel client = server.accept();
            client.configureBlocking(false);
            client.register(selector, SelectionKey.OP_READ);
        }
        if (key.isReadable()) {
            // 有数据可读
            SocketChannel client = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            client.read(buffer); // 不会卡死,因为已经确认有数据
        }
    }
    selector.selectedKeys().clear();
}

四种 IO 模型对比

模型阻塞非阻塞同步异步
同步阻塞(BIO)
同步非阻塞(NIO)
异步阻塞✅(没人用)
异步非阻塞(AIO)

实际上只有三种有意义的组合:BIO、NIO、AIO

生活中的类比

  • BIO:你去餐厅吃饭,服务员在你桌边等着你点菜,点完才走开
  • NIO(无复用):你去餐厅吃饭,服务员每隔 10 秒来问你一次「点菜了吗」
  • NIO(多路复用):你去餐厅吃饭,领班用对讲机监听所有桌,「有人举手才过去服务」

记住这个结论:Selector 是 NIO 的精髓——一个线程管 N 个连接

下一步

基于 VitePress 构建