文章目录
- Java IO模型详解
- 一、I/O的定义
- 1、计算机结构的视角
- 2、应用程序的视角
- 二、Java 中3种常见的 I/O 模型
- 1、同步阻塞 I/O(BIO)
- 2、同步非阻塞 I/O(NIO)
- ★ I/O 多路复用模型
- 3、异步非阻塞 I/O(AIO)
Java IO模型详解
一、I/O的定义
I/O 是 Input/Output 的首字母缩写,即输入/输出,它描述的是数据流动的过程。输入/输出是相对而言的。下面将从两个角度出发来进一步理解 IO:
1、计算机结构的视角
根据冯·诺依曼结构,计算机分为五大部分,分别是:控制器、运算器、存储器、输入设备、输出设备。
输入设备(如鼠标键盘)和输出设备(如显示器)都属于外设(外部设备),而像硬盘、网卡这种既属于输入设备又属于输出设备。
从计算机的角度出发的话,操作系统将从输入设备读取到的数据写入到输出设备,这就是一次完整的 I/O 过程。
即 I/O 描述了计算机核心(CPU和内存)与外部设备之间的数据转移的过程。
2、应用程序的视角
我们都知道,应用程序作为一个文件保存在磁盘中,只有加载到内存中成为一个进程才能够运行。
为了确保操作系统的安全性和稳定性,操作系统会将内存分为 内核空间 和 用户空间,进行内存隔离。
而我们运行的应用程序都是运行在用户空间的,只有内核空间才能进行系统态级别的资源有关的操作,比如文件管理、进程通信、内存管理等。也就是说,我们想要进行 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 中3种常见的 I/O 模型
- 同步 I/O,是指用户空间线程是主动发起 I/O 请求的一方,内核空间是被动接受方。
- 异步 I/O,则反过来,是指内核是主动发起 I/O 请求的一方,用户线程是被动接受方
- 阻塞 I/O,阻塞是指用户空间程序的执行状态,用户空间程序需等到 I/O 操作彻底完成。传统的 I/O 模型都是同步阻塞 I/O。在 Java 中,默认创建的socket都是阻塞的。
- 非阻塞 I/O,是指用户程序不需要等待内核IO操作完成后,内核立即返回给用户一个状态值,用户空间无需等到内核的 I/O 操作彻底完成,可以立即返回用户空间,执行用户的操作,处于非阻塞的状态。
1、同步阻塞 I/O(BIO)
应用程序中进程在发起 I/O 调用后至内核执行 I/O 操作返回结果之前,若发起系统调用的线程一直处于等待(阻塞)状态,则此次 I/O 操作为阻塞 I/O 。
阻塞 I/O 简称 BIO(Blocking IO)
如上图,同步阻塞 I/O 模型中,当用户线程发出 I/O 调用后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而此时用户线程也就会处于阻塞状态,用户线程交出 CPU 。当数据就绪之后,内核会将数据拷贝到用户空间,并返回结果给用户线程,用户线程才会接触阻塞状态。
- BIO 的优点:
程序简单,在阻塞等待数据期间,用户线程挂起。用户线程基本不会占用 CPU 资源。
- BIO 的缺点:
一般情况下,一个线程维护一个连接成功的 IO 流的读写。在并发量小时是没有问题的,但在并发很大时,BIO 是非常消耗系统资源的,这是行不通的。
2、同步非阻塞 I/O(NIO)
应用程序中进程在发起 I/O 调用后至内核执行 I/O 操作返回结果之前,若发起系统调用的线程不会等待而是立即返回,则此次 I/O 操作为非阻塞 I/O 模型。
非阻塞 I/O 简称 NIO(Non-Blocking IO)
如上图,同步非阻塞 I/O 模型中,当用户线程发起一个 read 操作后,并不需要等待,而是马上得到了一个结果。如果结果是一个调用失败的信息时,此时代表数据还没有准备好,他就可以再次发送 read 操作。一旦内核中的数据准备好了,并且又再次收到了用户线程的请求,那么他就马上将数据拷贝给了用户线程,然后返回。
在同步非阻塞 I/O 模型中,用户线程需要不断地询问内核数据是否就绪,也就是说同步非阻塞 I/O 模型不会交出 CPU ,而会一直占用 CPU。
即应用程序的线程需要不断的进行 I/O 系统调用,轮询数据是否已经准备好,如果没有准备好,直到完成系统调用为止。
- NIO的优点:
每次发起的 IO 系统调用,在内核的等待数据过程中可以立即返回。用户线程不会阻塞,实时性较好。
- NIO的缺点:
需要不断的重复发起 IO 系统调用,这种不断的轮询,将会不断地询问内核,这将占用大量的 CPU 时间,系统资源利用率较低。
★ I/O 多路复用模型
I/O 多路复用模型是目前使用得比较多的模型。Java 中的 NIO 可以看作是 I/O 多路复用模型(IO Multiplexing),而 Java 中的 NIO 可以通过的 Selector(选择器)达到一个线程管理多个客户端连接的效果。
IO 多路复用模型中,线程首先发起 select 调用,询问内核数据是否准备就绪,等内核把数据准备好了,用户线程再发起 read 调用。read 调用的过程(数据从内核空间 -> 用户空间)还是阻塞的。即在 read 调用之前会有一个 select 调用
IO 多路复用模型,通过减少无效的系统调用,减少了对 CPU 资源的消耗。
目前支持 IO 多路复用的系统调用,有 select,epoll 等等。select 系统调用,目前几乎在所有的操作系统上都有支持。
- select 调用 :内核提供的系统调用,它支持一次查询多个系统调用的可用状态。几乎所有的操作系统都支持。
- epoll 调用 :linux 2.6 内核,属于 select 调用的增强版本,优化了 IO 的执行效率。
3、异步非阻塞 I/O(AIO)
应用程序中在发起 I/O 调用后,用户进程立即返回,内核等待数据准备完成,然后将数据拷贝到用户进程缓冲区,最后发送信号告诉用户进程 I/O 操作执行完毕,则此次操作为异步 I/O。
异步 I/O 简称 AIO(Asynchronous I/O)
异步 I/O 真正实现了 I/O 全流程的非阻塞。用户线程完全不需要关心实际的整个 I/O 操作是如何进行的,只需要先发起一个请求,当接收内核返回的成功信号时表示 I/O 操作已经完成,可以直接去使用数据了。