Java NIO 总结
应用程序发起一次IO访问是分为两个阶段的:
- IO调用阶段:应用程序向内核发起系统调用。
- IO执行阶段:内核执行IO操作并返回。
- 数据准备阶段:内核等待IO设备准备好数据
- 数据拷贝阶段:将数据从内核缓冲区拷贝到用户空间缓冲区
现代操作系统将虚拟空间划分为内核空间和用户空间。
内核态:CPU可以访问内存所有数据, 包括外围设备, 例如硬盘,、网卡,CPU也可以将自己从一个程序切换到另一个程序。
用户态:只能受限的访问内存,不允许访问外围设备。CPU资源可以被其他程序获取。
用户态与内核态切换的开销:
- 保留用户态现场(上下文、寄存器、用户栈)
- 复制用户态参数
- 额外检查
- 执行内核态代码
- 复制内核态代码执行结果到用户态
- 恢复用户态现场
五种IO模型
同步:阻塞IO、非阻塞、IO复用、信号驱动IO
异步:异步IO
阻塞IO
应用程序发起系统调用后一直阻塞,直到内核将数据准备好,并将其从内核态复制到用户空间。
优点:简单,实现难度低,适用于并发量小的应用开发
缺点:整个过程都阻塞,程序性能较低
非阻塞IO
应用程序发起系统调用后,如果数据报没有准备好就会被立即返回一个EWOULDBLOCK错误码。进程收到该错误码之后如果判断内核数据还没有准备好就会继续发送系统调用。非阻塞IO会不断的主动询问内核数据是否准备好了。
优点:简单,易实现。在等待数据的过程中可以做别的事情。
缺点:轮询发送系统调用,消耗cpu资源。不适合大并发量的应用程序。
IO复用模型
不需要进程轮询来查询数据是否准备好了,而是有人帮忙来询问。帮忙的人就是select。
多个进程的IO注册到一个复用器(select)上,然后用一个进程监听该select,select会监听所有注册进来的IO。如果没有数据则阻塞,而如果任一IO在内核缓冲区中就会返回可读条件,然后进程再进行系统调用。
优点:只有一个进行复杂状态监听,性能好,适合高并发
缺点:复杂,实现难度大
信号驱动IO模型
因为io复用模型在第一和第二阶段都会阻塞,所以得想办法减少阻塞来提高性能。
进程发起IO调用后向内核注册一个信号处理程序,然后立即返回不阻塞,当内核处理好数据之后会发送一个信号给进程。与IO复用模型的主要区别在等待数据的过程无阻塞。
优点:采用回调机制,等待数据阶段无阻塞,适用于高并发
缺点:模型复杂,实现困难
异步IO模型
在数据复制的阶段也无阻塞,真正的IO
优点:整个过程不阻塞,适用于高并发应用
缺点:需要操作系统底层支持,模型复杂且实现难度大。
总结
参考:
https://www.cnblogs.com/chenssy/p/15382938.html