IO 的字面意思是读/写数据,IO 模型是读/写数据的方式。常用到的读/写数据方式有:同步阻塞 IO、同步非阻塞 IO、IO 多路复用、信号驱动、异步 IO
~
本篇内容包括:Java IO 与 IO 模型、五种 IO 模型、三种 Java IO 模型。
文章目录
- 一、Java IO 与 IO 模型
- 1、IO 与 IO 模型的关系
- 2、IO 的流程
- 二、五种 IO 模型
- 1、同步阻塞
- 2、非阻塞
- 3、多路复用 IO
- 4、信号驱动
- 5、异步IO
- 三、三种 Java IO 模型
- 1、BIO
- 2、NIO
- 3、AIO
- 4、适用场景分析
一、Java IO 与 IO 模型
1、IO 与 IO 模型的关系
提到 IO,就要说 IO 模型,否则就像学 Java,不讲面向对象一样,是很难全面的理解它的精髓的。
IO 的字面意思是读/写数据,IO 模型是读/写数据的方式。常用到的读/写数据方式有:同步阻塞 IO、同步非阻塞 IO、IO 多路复用、信号驱动、异步 IO
Java IO 的类库就是针对不同 IO 模型封装好的工具类。
Java IO 系统针对三种 IO 模型开发和工具类:
- BIO(同步阻塞 IO)
- NIO(同步非阻塞 IO 和 IO 多路复用)
- AIO(异步 IO)
这三个模型也是 Java IO 系统演化经历的三个阶段,不同 IO 模型的类库分别理解即可。
2、IO 的流程
在区分各种 IO 模型的区别之前,我们需要先了解一个 IO,在操作系统的层面究竟发生了哪些事情。注意,我们说的 IO 模型对 IO 的优化,都是基于读 IO。
假设我们需要等待 Socket 的数据,也就是说当前是一个读操作的 IO,那么在操作系统层面需要分为两步:
- 等待数据被写入 Socket 的缓冲区中
- 将 Socket 缓冲区中的数据拷贝到应用程序中
我们可以很容易的发现,第一步是由对方决定的,对方决定了什么时候把数据发送过来。第二步是由操作系统决定的,操作系统需要陷入内核态拷贝数据。
而我们的同步异步、阻塞非阻塞也是从这里出发的。
二、五种 IO 模型
1、同步阻塞
在同步阻塞的 IO 模型中,在第一阶段(等待数据被写入 Socket 的缓冲区中),操作系统会把当前的进程设置为阻塞状态,直到缓冲区被写入数据这个进程才被唤醒。
这也就造成了一个问题,当操作系统把这个进程置为阻塞态的时候,这个进程就什么事都做不了了。
2、非阻塞
为了解决 “同步阻塞” 进程可能无期限阻塞的情况,于是产生了 “同步非阻塞”。
在这种 IO 模型中,如果发现这个 Socket 里面没有准备好的数据就返回一个错误,而不是把这个进程设置为阻塞状态。
也就是说我们可以轮询这个 Socket 查看有无准备好的数据。
3、多路复用 IO
假设此时我们的服务器需要管理很多的 IO 请求,如果给每一个 IO 都分配一个进程/线程,自旋的等待有无数据到来,无疑是很浪费资源的。
如果我们用一个进程,轮询所有的 IO 请求,又会使 IO 的响应变得很慢。
因此,产生了多路复用 IO
多路复用 IO 就是用一条线程,同时监听多个 IO 请求,并且在有 IO 请求产生的时候返回。
注意,虽然我们的 IO 多路复用也会阻塞,但是这里的阻塞是应用层面的,也就是说在多路复用的方法上进行阻塞,而不是在操作系统层面去阻塞。
4、信号驱动
在以上的 IO 模型中,都是需要应用自己去询问 Socket 是否已经准备好了数据,而信号驱动就是由 Socket 主动告诉你有没有准备好数据。
这么做的好处在于第一阶段程序不需要任何的等待与轮询,只需要在收到了信号通知之后去处理这个 IO 即可。
5、异步IO
在以上的四种 IO 操作中,无论第一个阶段是如何进行优化的,在第二阶段都需要陷入内核态来拷贝数据。
异步 IO 就是为了解决这个问题。
异步 IO 在解决了阻塞这个问题后,同样把陷入内核态来拷贝数据的时间也节省了。
这样,当程序需要进行 IO 的时候,只需要发出 IO 的请求,然后就可以继续执行后面的代码,直到 IO 完成操作系统会通知程序。
同样的,异步 IO 是非阻塞的。
三、三种 Java IO 模型
1、BIO
BIO 全称是 Blocking IO,是 JDK1.4 之前的传统 IO 模型,本身是同步阻塞模式。线程发起 IO 请求后,一直阻塞 IO,直到缓冲区数据就绪后,再进入下一步操作。针对网络通信都是一请求一应答的方式,虽然简化了上层的应用开发,但在性能和可靠性方面存在着巨大瓶颈,试想一下如果每个请求都需要新建一个线程来专门处理,那么在高并发的场景下,机器资源很快就会被耗尽。
BIO 通信模型图:
2、NIO
NIO 也叫 Non-Blocking IO 是同步非阻塞的 IO 模型。线程发起 IO 请求后,立即返回。同步指的是必须等待 IO 缓冲区内的数据就绪,而非阻塞指的是,用户线程不原地等待 IO 缓冲区,可以先做一些其他操作,但是要定时轮询检查 IO 缓冲区数据是否就绪。
Java 中的 NIO 是 new IO的意思。其实是 NIO 加上 IO 多路复用技术。普通的 NIO 是线程轮询查看一个 IO 缓冲区是否就绪,而 Java 中的 new IO 指的是线程轮询地去查看一堆 IO 缓冲区中哪些就绪,这是一种 IO 多路复用的思想。IO多路复用模型中,将检查 IO 数据是否就绪的任务,交给系统级别的 select 或 epoll 模型,由系统进行监控,减轻用户线程负担。
NIO主要有 buffer、channel、selector 三种技术的整合,通过零拷贝的 buffer 取得数据,每一个客户端通过 channel 在 selector(多路复用器)上进行注册。服务端不断轮询 channel 来获取客户端的信息。channel 上有 connect、accept(阻塞)、read(可读)、write(可写)四种状态标识。根据标识来进行后续操作。所以一个服务端可接收无限多的 channel。不需要新开一个线程。大大提升了性能。
NIO 通信模型图:
3、AIO
AIO 是真正意义上的异步非阻塞 IO 模型。上述 NIO 实现中,需要用户线程定时轮询,去检查 IO 缓冲区数据是否就绪,占用应用程序线程资源,其实轮询相当于还是阻塞的,并非真正解放当前线程,因为它还是需要去查询哪些 IO 就绪。而真正的理想的异步非阻塞IO应该让内核系统完成,用户线程只需要告诉内核,当缓冲区就绪后,通知我或者执行我交给你的回调函数。
AIO 可以做到真正的异步的操作,但实现起来比较复杂,支持纯异步 IO 的操作系统非常少,目前也就 windows 是 IOCP 技术实现了,而在 Linux上,底层还是是使用的 epoll 实现的。
与 NIO 不同,当进行读写操作时,只需直接调用 API 的 read 或 write 方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入 read 方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将 write 方法传递的流写入完毕时,操作系统主动通知应用程序。即可以理解为, read/write 方法都是异步的,完成后会主动调用回调函数。 在 JDK1.7 中,这部分内容成为 AIO。
4、适用场景分析
BIO 方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4 以前的唯一选择,但程序直观简单易理解。
NIO 方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4 开始支持。
AIO 方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用 OS 参与并发操作,编程比较复杂,JDK7 开始支持。