1. BIO(同步阻塞 I/O)
核心原理
- 传统的 I/O 模型,线程与 I/O 连接一一对应:
- 服务端启动一个监听线程,接收客户端连接;
- 每建立一个客户端连接,就创建一个新线程(或从线程池取线程)处理该连接的 I/O 操作(读 / 写);
- 线程在执行 I/O 操作时(如
read()),若数据未就绪,会被阻塞(挂起),直到数据就绪或超时。
通俗示例
餐厅服务员(线程)一对一服务顾客(客户端连接):顾客下单后(I/O 请求),服务员站在旁边等待厨师做菜(数据就绪),期间不能服务其他顾客(线程阻塞)。
核心特点
- 模型简单,开发成本低(API 直观);
- 线程阻塞严重:即使连接无数据交互,线程也会被占用,资源利用率极低;
- 并发能力弱:线程是稀缺资源(JVM 线程数上限通常几千),无法支撑万级以上并发连接
-
Java 实现示例(BIO 服务端)
java运行import java.net.ServerSocket; import java.net.Socket;public class BioServer {public static void main(String[] args) throws Exception {ServerSocket serverSocket = new ServerSocket(8080);System.out.println("BIO 服务端启动,监听 8080 端口");while (true) {// 阻塞:等待客户端连接(无连接时线程挂起)Socket clientSocket = serverSocket.accept();System.out.println("新客户端连接:" + clientSocket.getInetAddress());// 每个连接创建一个新线程处理(线程阻塞于 read/write)new Thread(() -> {try {// 读取客户端数据(阻塞:无数据时线程挂起)byte[] buffer = new byte[1024];clientSocket.getInputStream().read(buffer);System.out.println("收到数据:" + new String(buffer).trim());// 响应客户端(阻塞:未写完时线程挂起)clientSocket.getOutputStream().write("BIO 响应".getBytes());clientSocket.close();} catch (Exception e) {e.printStackTrace();}}).start();}} }适用场景
- 并发量低(几百连接以内)、简单业务场景(如内部系统通信、FTP 服务);
- 开发效率优先,无需高并发支持(如小型工具、测试接口)。
2. NIO(同步非阻塞 I/O)
核心原理
Java NIO 是 JDK 1.4 引入的 “新 I/O”,核心是 “多路复用”(Selector),解决 BIO 线程浪费的问题:- 服务端用一个 Selector(选择器) 管理所有客户端连接(Channel);
- 每个连接对应一个 Channel(通道),Channel 是双向的(可读可写),且支持非阻塞;
- 线程通过 Selector 轮询所有 Channel,仅处理 “数据就绪” 的 Channel(如可读、可写),无需阻塞等待;
- 核心组件:
Selector(多路复用器)、Channel(通道)、Buffer(缓冲区)—— 数据必须通过 Buffer 读写,Channel 不直接操作数据。
通俗示例
一个服务员(线程)用一个记事本(Selector)管理所有桌子(Channel):服务员每隔一段时间(轮询)看记事本,只处理 “需要服务” 的桌子(数据就绪的 Channel),其他时间可以做别的(非阻塞),无需一对一等待。核心特点
- 同步非阻塞:线程不会因单个 I/O 操作阻塞,可同时管理大量 Channel;
- 多路复用:一个线程即可支撑万级以上并发连接(资源利用率极高);
- 开发复杂度高于 BIO:需理解 Selector、Channel、Buffer 的协同工作机制;
- 同步模型:线程需主动轮询 Selector 获取就绪 Channel,仍需 “主动等待” 结果(区别于 AIO 的异步通知)。
核心组件说明
组件 作用 Selector 多路复用器,管理所有注册的 Channel,检测 Channel 的就绪状态(可读、可写) Channel 双向 I/O 通道(替代 BIO 的 Stream),支持非阻塞,可注册到 Selector Buffer 数据缓冲区(字节数组封装),Channel 读写数据必须通过 Buffer(BIO 可直接读写) SelectionKey 注册到 Selector 的 Channel 对应的 “键”,标记 Channel 的就绪状态(如 OP_READ) Java 实现示例(NIO 服务端)
java运行import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator;public class NioServer {public static void main(String[] args) throws Exception {// 1. 创建 Selector(多路复用器)Selector selector = Selector.open();// 2. 创建 ServerSocketChannel(监听通道),设置非阻塞ServerSocketChannel serverChannel = ServerSocketChannel.open();serverChannel.bind(new InetSocketAddress(8080));serverChannel.configureBlocking(false);// 3. 注册 ServerSocketChannel 到 Selector,关注“接收连接”事件(OP_ACCEPT)serverChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("NIO 服务端启动,监听 8080 端口");while (true) {// 4. 轮询 Selector:阻塞等待就绪事件(可设置超时时间)selector.select(); // 无就绪事件时线程阻塞,有事件时立即返回// 5. 遍历就绪的 SelectionKey(就绪的 Channel)Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();while (keyIterator.hasNext()) {SelectionKey key = keyIterator.next();keyIterator.remove(); // 移除已处理的 key,避免重复处理// 6. 处理“接收连接”事件(ServerSocketChannel 就绪)if (key.isAcceptable()) {ServerSocketChannel server = (ServerSocketChannel) key.channel();// 接收客户端连接(非阻塞:无连接时返回 null,此处已就绪故非 null)SocketChannel clientChannel = server.accept();clientChannel.configureBlocking(false); // 设置为非阻塞System.out.println("新客户端连接:" + clientChannel.getRemoteAddress());// 注册客户端 Channel 到 Selector,关注“读数据”事件(OP_READ)clientChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));}// 7. 处理“读数据”事件(客户端 Channel 就绪)else if (key.