0%

IO 简介

阻塞 I/O 模型

阻塞 I/O 模型是常见的 I/O 模型, 在读写数据时客户端会发生阻塞

工作流程为

在用户线程发出 I/O 请求之后, 内核会检查数据是否就绪, 此时用户线程一直阻塞等待内存数据就绪
在内存数据就绪后, 内核将数据复制到用户线程中, 并返回 I/O 执行结果到用户线程, 此时用户线程将解除阻塞状态并开始处理数据

当执行 data = socket.read() 时, 如果内核数据没有就绪, Socket 线程就会一直阻塞在 read() 中等待内核数据就绪

非阻塞 I/O 模型

用户线程在发起一个 I/O 操作后, 无须阻塞便可以马上得到内核返回的一个结果, 如果内核返回的结果为 false, 则表示内核数据还没准备好, 需要稍后再发起 I/O 操作, 一旦内核中的数据准备好了, 并且再次收到用户线程的请求, 内核就会立刻将数据复制到用户线程中并将复制的结果通知用户线程

非阻塞 I/O 模型中, 用户线程需要不断询问内核数据是否就绪, 在内存数据还未就绪时, 用户线程可以处理其他任务, 在内核数据就绪后可立即获取数据并进行相应的操作

1
2
3
4
5
6
7
8
9
10
while(true) {
data = socket.read();
// 判断数据是否就绪
if (data == true) {
// 获取并处理内核数据
break;
} else {
// 内核数据未就绪, 用户线程处理其他任务
}
}

多路复用 I/O 模型

多路复用 I/O 模型是多线程并发编程用得较多的模型, Java NIO 就是基于多路复用 I/O 模型实现的, 在多路复用 I/O 模型中会有一个被称为 Selector 的线程不断轮询多个 Socket 的状态, 只有在 Socket 有读写事件时, 才会通知用户线程进行 I/O 读写操作

多路复用 I/O 模型中只需一个线程就可以管理多个 Socket (阻塞 I/O 模型和非阻塞 I/O 模型需要为每个 Socket 都建立一个单独的线程处理该 Socket 上的数据) , 并且在真正有 Socket 读写事件时才会使用操作系统的 I/O 资源, 大大节约了系统资源, 在连接数众多且消息体不大的情况下有很大的优势, 尤其在物联网领域比如车载设备实时位置, 智能家电状态等定时上报状态且字节数较少的情况下优势更加明显, 一般一个经过优化后的 1632GB 服务器能承载约 10 万台设备连接

每个用户线程中都通过 selector.select() 查询当前通道是否有事件到达, 如果没有, 则会一直阻塞

非阻塞 I/O 模型在每个用户线程中都进行 Socket 状态检查, 而在多路复用 I/O 模型中是在系统内核中进行 Socket 状态检查的, 这也是多路复用 I/O 模型比非阻塞 I/O 模型效率高的原因, 但是在事件响应体 (消息体) 很大时, Selector 线程就会成为性能瓶颈, 导致后续的事件迟迟得不到处理, 影响下一轮的事件轮询, 在实际应用中, 在多路复用方法体内一般不建议做复杂逻辑运算, 只做数据的接收和转发, 将具体的业务操作转发给后面的业务线程处理

信号驱动 I/O 模型

用户线程发起一个 I/O 请求操作时, 系统会为该请求对应的 Socket 注册一个信号函数, 然后用户线程可以继续执行其他业务逻辑, 在内核数据就绪时, 系统会发送一个信号到用户线程, 用户线程在接收到该信号后, 会在信号函数中调用对应的 I/O 读写操作完成实际的 I/O 请求操作

异步 I/O 模型

在异步 I/O 模型中, 用户线程会发起一个 asynchronous read 操作到内核, 内核在接收到 synchronous read 请求后会立刻返回一个状态, 来说明请求是否成功发起, 在此过程中用户线程不会发生任何阻塞, 接着, 内核会等待数据准备完成并将数据复制到用户线程中, 在数据复制完成后内核会发送一个信号到用户线程, 通知用户线程 asynchronous 读操作已完成, 在异步 I/O 模型中, 用户线程不需要关心整个 I/O 操作是如何进行的, 只需发起一个请求, 在接收到内核返回的成功或失败信号时说明 I/O 操作已经完成, 直接使用数据即可

在异步 I/O 模型中, I/O 操作的两个阶段 (请求的发起, 数据的读取) 都是在内核中自动完成的, 最终发送一个信号告知用户线程 I/O 操作已经完成, 用户直接使用内存写好的数据即可, 不需要再次调用 I/O 函数进行具体的读写操作, 因此在整个过程中用户线程不会发生阻塞

在信号驱动模型中, 用户线程接收到信号便表示数据已经就绪, 需要用户线程调用 I/O 函数进行实际的 I/O 读写操作, 将数据读取到用户线程, 而在异步 I/O 模型中, 用户线程接收到信号便表示 I/O 操作已经完成 (数据已经被复制到用户线程) , 用户可以开始使用该数据了

异步 I/O 需要操作系统的底层支持, 在 Java 7 中提供了 Asynchronous I/O 操作

API

File, OutputStream, InputStream, Writer, Reader 和接口 Serializable