在我们学习Java网络编程或者去面试的时候,总是能遇到这三个字母:BIO、NIO、AIO

它们是什么?又有什么区别?我们在什么地方会使用到呢?

我们先来理解这三个概念:

BIO:一个连接一个线程,线程会一直等待(阻塞)

NIO:一个线程处理多个连接,有数据才处理(非阻塞)

AIO:发起请求后就去干别的,完成会自动回调(异步)

BIO(阻塞I/O)

什么是BIO?

BIO就是同步阻塞I/O,是JDK1.4之前唯一的网络编程模型。

  • 同步:应用程序要亲自参与数据的搬运
  • 阻塞:线程在等待数据时会被“卡住”,什么都做不了

BIO的工作原理

// 简化版代码示例
while (true) {
    Socket socket = serverSocket.accept(); // 阻塞,等待客户端连接
    // 为每个连接创建一个新线程
    new Thread(() -> {
        InputStream input = socket.getInputStream();
        byte[] buffer = new byte[1024];
        int len = input.read(buffer); // 阻塞,等待数据到达
        // 处理数据...
    }).start();
}

工作流程

  1. 服务器启动,等待客户端连接
  2. 客户端连接后,服务器创建专属线程为其服务
  3. 该线程阻塞在read()方法上,直到客户端发送数据
  4. 处理完数据后,线程可能继续阻塞等待下一次数据

日常开发都在用

// Spring MVC控制器 - 底层就是BIO
@RestController
public class UserController {
    @GetMapping("/user/{id}")
    public User getUser(@PathVariable Long id) {
        // 每个请求占用一个线程直到完成
        return userService.findById(id);
    }
}

// 数据库操作 - 也是BIO
User user = userRepository.findById(1L); // 线程等待数据库响应

使用场景

  • 内部管理系统
  • 传统Web应用
  • 批处理任务

优点

  • 编程简单,容易理解
  • 适合初学者学习网络编程

缺点

  • 每个连接都需要一个线程,资源消耗大
  • 并发量高时,线程切换开销巨大

NIO(非阻塞I/O)

什么是NIO?

NIO是同步非阻塞I/O,从JDK1.4开始引入,解决了BIO的性能瓶颈。

  • 同步:应用程序还是要参与数据搬运
  • 非阻塞:线程不会傻等,没有数据就立即返回

NIO的三大核心组件

1. Channel(通道)— 数据传输的管道

与传统Stream的区别:

  • Stream是单向的(Input/Output分开)
  • Channel是双向的,既可以读也可以写
2. Buffer(缓冲区)— 数据的临时仓库

所有数据都通过Buffer进行读写,提供了统一的数据处理方式。

3. Selector(选择器)— 高效的调度中心

核心中的核心!一个Selector可以监控成千上万的Channel,发现哪个Channel有数据就绪了,就通知相应的线程来处理。

NIO的工作原理

// 简化版工作流程
Selector selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false); // 设置为非阻塞模式

// 将通道注册到选择器,关注连接事件
serverChannel.register(selector, SelectionKey.OP_ACCEPT);

while (true) {
    selector.select(); // 阻塞,直到有事件发生
    Set<SelectionKey> selectedKeys = selector.selectedKeys();
    
    for (SelectionKey key : selectedKeys) {
        if (key.isAcceptable()) {
            // 处理连接事件
        } else if (key.isReadable()) {
            // 处理读事件
        }
    }
}

工作流程(Reactor模式):

  1. 创建Selector,相当于“调度中心”
  2. 将多个Channel注册到Selector上,并声明关心的事件
  3. Selector轮询所有注册的Channel
  4. 当某个Channel有事件就绪时,Selector通知应用程序
  5. 应用程序处理对应事件

NIO 高并发系统的核心

// Spring Cloud Gateway - 底层NIO
spring:
  cloud:
    gateway:
      routes:
      - id: user_route
        uri: lb://user-service
        predicates:
        - Path=/user/**

// WebSocket聊天 - 底层NIO  
@ServerEndpoint("/chat")
public class ChatEndpoint {
    @OnMessage
    public void onMessage(String message) {
        // 一个线程处理成千上万连接
        broadcast(message);
    }
}

使用场景

  • API网关
  • 实时聊天
  • 微服务调用

优点

  • 一个线程处理多个连接,资源利用率高
  • 适合高并发场景
  • 性能远超BIO

缺点

  • 编程复杂,理解成本高
  • API使用比较繁琐

AIO(异步I/O)

什么是AIO?

AIO是异步非阻塞I/O,从JDK1.7开始引入。

  • 异步:应用程序发起请求后就直接返回,内核完成所有工作后会主动回调
  • 非阻塞:整个过程都不会阻塞线程

AIO的工作原理

// 异步读操作示例
AsynchronousSocketChannel channel = AsynchronousSocketChannel.open();
ByteBuffer buffer = ByteBuffer.allocate(1024);

// 发起异步读操作,并设置回调函数
channel.read(buffer, null, new CompletionHandler<Integer, Object>() {
    @Override
    public void completed(Integer result, Object attachment) {
        // 数据读取完成后的回调处理
        System.out.println("数据读取完成,长度:" + result);
    }
    
    @Override
    public void failed(Throwable exc, Object attachment) {
        // 读取失败的处理
        exc.printStackTrace();
    }
});

工作流程(Proactor模式):

  1. 应用程序发起异步I/O请求
  2. 请求立即返回,应用程序继续执行其他任务
  3. 内核完成所有工作(包括数据准备和拷贝)
  4. 内核主动调用应用程序设置的回调函数
  5. 在回调函数中处理数据

AIO:特定场景使用

// 大文件上传 - 适合AIO
public CompletableFuture<String> uploadLargeFile(File file) {
    return CompletableFuture.supplyAsync(() -> {
        // 异步处理,不阻塞线程
        return cloudStorage.upload(file);
    });
}

使用场景

  • 大文件处理
  • 云存储服务
  • 金融交易

优点

  • 真正的异步,性能理论上最好
  • 不会阻塞任何线程

缺点

  • 编程模型最复杂
  • Linux平台支持不如Windows完善
  • 实际很少使用

总结

三种模式对比

特性 BIO NIO AIO
全称 同步阻塞I/O 同步非阻塞I/O 异步非阻塞I/O
编程难度 简单 复杂 很复杂
线程模型 一个连接一个线程 一个线程处理多个连接 回调机制,少量线程
性能 理论上最高
适用场景 连接数少 高并发、短连接 高并发、长连接、大数据量

技术选型指南

场景 选择 原因
普通Web应用 BIO 开发简单
高并发API NIO 性能好
实时通信 NIO 支持多连接
文件处理 AIO 异步优势

技术是为业务服务的!没有最好的技术,只有最适合业务场景的技术。

日常开发用BIO,高并发用NIO,特殊需求用AIO。不用纠结,大部分情况下框架都帮你选好了。

本文首发于公众号:程序员刘大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!

📌往期精彩

《代码里全是 new 对象,真的很 Low 吗?我认真想了一晚》

《Java 开发必看:什么时候用 for,什么时候用 Stream?》

《这 5 个冷门 HTML 标签,让我直接删了100 行 JS 代码》

《Vue 组件通信的 8 种最佳实践,你知道几种?》

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐