1.IO前置知识
为了保证操作系统的稳定性和安全性,一个进程的地址空间划分为用户空间(User space)和内核空间(Kernel space) 。
我们平常运行的应用程序都是运行在用户空间的,只有内核空间才能进行系统级别的资源有关的操作,比如文件管理、进程通信、内存管理等。也就是说,我们想要进行IO操作,一定要依赖内核空间的能力。用户空间的程序不能直接访问内核空间,当想要执行IO操作时,由于没有执行这些操作的权限,只能发起系统调用请求操作系统帮忙完成。
用户进程想要执行IO操作的话,必须通过系统调用来间接访问内核空间。
平常开发过程中接触最多的是磁盘IO(读写文件)和网络IO(网络请求和响应) 。
2.I/O调用步骤
- 内核等待I/O设备准备好数据
- 内核数据从内科空间拷贝到用户空间
3.UNIX系统下的IO模型
UNIX系统下,IO模型一共有5种:同步阻塞I/O、同步非阻塞I/O、I/O多路复用、信号驱动I/O和异步I/O。
同步阻塞IO模型(BIO)
同步阻塞IO模型中,应用程序发起read调用后,会一直阻塞,直到内核把数据拷贝到用户空间。
在客户端连接数量不高的情况下,是没问题的。但是当面对十万甚至百万级连接的时候,传统的BIO模型是无能为力的。因此,我们需要一种更高效的I/O处理模型来应对更高的并发量。
Java中的NIO于Java 1.4中引入,对用的java.nio包,提供了Channel,Selector,Buffer等抽象。NIO中的N可以理解为Non-blocking,不单纯是New,它是支持面向缓冲的,基于通道的I/O操作方法,对于高负载、高并发的(网络)应该用,应使用NIO。
Java中的NIO可以看做是I/O多路复用模型,也有很多人认为,Java中的NIO属于同步非阻塞IO模型。
同步非阻塞IO模型(NIO)
同步非阻塞IO模型中,应用程序会一直发起read调用,在内核准备数据和数据就绪的时间段内应用程序是不阻塞的,但是等待数据从内核空间拷贝到用户空间的这段时间里,线程依然是阻塞的,直到在内核把数据全部拷贝到用户空间。
相比同步阻塞IO模型,同步非阻塞IO模型确实有了很大改进,通过轮询操作,避免了一直阻塞。但是这种IO模型同样存在问题:应用程序需要不断进行I/O系统调用轮询数据是否已经准备好,这个过程十分消耗CPU资源。
I/O多路复用模型
线程首先发起select调用,询问内核数据是否准备就绪,等内核把数据准备好了,用户线程再发起read调用,read调用的过程(数据从内核空间->用户空间)还是阻塞的。
目前支持IO多路复用的系统调用,有select,epoll等,select系统调用,目前几乎在所有的操作系统上都有支持。
- select 调用:内核提供的系统调用,它支持一次查询多个系统调用的可用状态。几乎所有的操作系统都支持。
- epoll 调用:linux 2.6内核,属于select调用的增强版本,优化了IO的执行效率。
IO多路复用模型,通过减少了无效的系统调用,减少了对CPU资源的消耗。
Java中的NIO,有一个非常重要的选择器(Selector)的概念,也可以被称为多路复用器。通过它只需要一个线程便可以管理多个客户端连接,当客户端数据到了之后,才会为其服务。
异步IO(AIO)
异步IO是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
目前来说AIO的应用还不是很广泛,Netty之前也尝试使用过AIO,不过又放弃了。这是因为,Netty使用了AIO之后,在Linux系统上的性能并没有多少提升。
各种IO模型总结
BIO:
- 同步阻塞IO,一个连接一个线程
- 发起请求一直阻塞,一般通过线程池改善
NIO:
- 同步非阻塞IO,一个请求一个线程
- 多个连接多路复用一个线程
AIO:
- 异步非阻塞IO,一个有效请求一个线程
- IO请求后立即返回,操作结束后,回调通知