面试必问的BIO、NIO、AIO,原来在实际项目中这样用!
搞不懂Java网络编程的BIO、NIO、AIO?5分钟帮你理清思路!从Spring MVC到微服务网关,不同场景该用哪种I/O模型?为什么高并发要用NIO?大文件处理适合AIO?看完这篇就够了!
在我们学习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();
}
工作流程:
- 服务器启动,等待客户端连接
- 客户端连接后,服务器创建专属线程为其服务
- 该线程阻塞在read()方法上,直到客户端发送数据
- 处理完数据后,线程可能继续阻塞等待下一次数据
日常开发都在用
// 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模式):
- 创建Selector,相当于“调度中心”
- 将多个Channel注册到Selector上,并声明关心的事件
- Selector轮询所有注册的Channel
- 当某个Channel有事件就绪时,Selector通知应用程序
- 应用程序处理对应事件
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模式):
- 应用程序发起异步I/O请求
- 请求立即返回,应用程序继续执行其他任务
- 内核完成所有工作(包括数据准备和拷贝)
- 内核主动调用应用程序设置的回调函数
- 在回调函数中处理数据
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?》
更多推荐


所有评论(0)