Java里面的IO模型种类较多,主要包括BIO,NIO和AIO,每一个IO模型都有不同的地方,那么这些IO模型是如何演变呢,底层的原理又是怎样的呢? 本文咱们就来聊聊。java
BIO全称是Blocking IO,是JDK1.4以前的传统IO模型,自己是同步阻塞模式,针对网络通讯都是一请求一应答的方式,虽然简化了上层的应用开发,但在性能和可靠性方面存在着巨大瓶颈,试想一下若是每一个请求都须要新建一个线程来专门处理,那么在高并发的场景下,机器资源很快就会被耗尽,固然,咱们能够经过线程池来优化这种状况,但即便是这样,仍然改变不了阻塞IO的根本问题,就是在IO执行的两个阶段都被block了。拿一个read操做来举例子,在linux中,应用程序向linux发起read操做,会经历两个步骤:linux
第一个阶段linux内核首先会把须要读取的数据加载到操做系统内核的缓冲区中(Linux文件系统是缓存IO,也称标准IO)编程
第二个阶段应用程序拷贝内核里面的数据到本身的用户空间中windows
若是是socket操做,相似也会经历两个步骤:api
第一个阶段:一般涉及等待网络上的数据分组包到达,而后被复制到内核的缓冲区缓存
第二个阶段:把数据从内核缓冲区,从内核缓冲区拷贝到用户进程的内存空间里面网络
同步阻塞IO之因此效率低下,就是由于在这两个阶段,用户的线程或者进程都是阻塞的,期间虽然不占cpu资源,但也意味着该线程也不能再干其余事。有点站着茅坑不拉屎的感受,本身暂时不用了,也不让别人用。多线程
图示以下:并发
因为BIO的缺点,致使Java在JDK1.0至JDK3.0中,网络通讯模块的性能一直是短板,因此不少人更倾向于使用C/C++开发高性能服务端。为了强化Java在服务端的市场,终于在JSR-51也就是JDK4.0的时候发布了Java NIO,能够支持非阻塞IO。并新增了java.nio的包,提供不少异步开发的API和类库。框架
主要的类和接口以下:
(1)进行异步IO操做的缓冲区ByteBuffer
(2)进行异步IO操做的管道Pipe
(3)进行各类IO操做的Channel,主要包括ServerSocketChannel和SocketChannel
(4)实现非阻塞IO的多路复用器Selector
NIO主要有buffer、channel、selector三种技术的整合,经过零拷贝的buffer取得数据,每个客户端经过channel在selector(多路复用器)上进行注册。服务端不断轮询channel来获取客户端的信息。channel上有connect,accept(阻塞)、read(可读)、write(可写)四种状态标识。根据标识来进行后续操做。因此一个服务端可接收无限多的channel。不须要新开一个线程。大大提高了性能。
新的nio类库,促进了异步非阻塞编程的发展和应用,但仍然有一些不足之处:
(1)没有统一的文件属性,例如读写权限
(2)api能力比较弱,例如目录的及联建立和递归遍历,每每须要本身完成。
(3)底层操做系统的一些高级API没法使用
(4)全部的文件操做都是同步阻塞调用,在操做系统层面上并非异步文件读写操做。
Java里面的NIO其实采用了多路复用的IO模式,多路复用的模式在Linux底层实际上是采用了select,poll,epoll的机制,这种机制能够用单个线程同时监听多个io端口,当其中任何一个socket的数据准备好了,就能返回通知用户线程进行读取操做,与阻塞IO阻塞的是每个用户的线程不同的地方是,多路复用只须要阻塞一个用户线程便可,这个用户线程一般咱们叫它Selector,其实底层调用的是内核的select,这里面只要任何一个IO操做就绪,就能够唤醒select,而后交由用户线程处理。用户线程读取数据这个过程仍然是阻塞的,多路复用技术只是在第一个阶段能够变为非阻塞调用,但在第二个阶段拷贝数据到用户空间,其实仍是阻塞的,多路复用技术的最大特色是使用一个线程就能够处理不少的socket链接,尽管性能上不必定提高,但支持并发能力却大大加强了。
图示以下:
AIO,实际上是NIO的改进优化,也被称为NIO2.0,在2011年7月,也就是JDK7的版本中发布,它主要提供了三个方面的改进:
(1)提供了可以批量获取文件属性的api,经过SPI服务,使得这些API具备平台无关性。
(2)提供了AIO的功能,支持基于文件的异步IO操做和网络套接字的异步操做
(3)完成了JSR-51定义的通道功能等。
AIO 经过调用accept方法,一个会话接入以后再次调用(递归)accept方法,监听下一次会话,读取也再也不阻塞,回调complete方法异步进行。再也不须要selector 使用channel线程组来接收。
从NIO上面咱们能看到,对于IO的两个阶段的阻塞,只是对于第一个阶段有所改善,对于第二个阶段在NIO里面仍然是阻塞的。而真正的理想的异步非阻塞IO(AAIO)要作的就是,将IO操做的两个阶段都所有交给内核系统完成,用户线程只须要告诉内核,我要读取一块数据,请你帮我读取,读取完了放在我给你的地址里面,而后告诉我一声就能够了。
AIO能够作到真正的异步的操做,但实现起来比较复杂,支持纯异步IO的操做系统很是少,目前也就windows是IOCP技术实现了,而在Linux上,目前有不少开源的异步IO库,例如libevent、libev、libuv,但基本都不是纯的异步IO操做,底层仍是是使用的epoll实现的。
图示以下:
既然Java拥有了各类IO体系,那么为何还会出现Netty这种框架呢?
Netty出现的主要缘由,以下:
(1)Java NIO类库和API繁杂众多,使用麻烦。
(2)Java NIO封装程度并不高,经常须要配合Java多线程编程来使用,这是由于NIO编程涉及到Reactor模式。
(3)Java NIO异常体系不完善,如客户端面临断连,重连,网络闪断,半包读写,网络阻塞,异常码流等问题,虽然开发相对容易,可是可靠性和稳定性并不高。
(4)Java NIO自己的bug,修复较慢。
注意,真正的异步非阻塞io,是须要操做系统层面支持的,在windows上经过IOCP实现了真正的异步io,因此Java的AIO的异步在windows平台才算真正获得了支持,而在Linux系统中,仍然用的是epoll模式,因此在Linux层面上的AIO,并非真正的或者纯的异步IO,这也是Netty里面为何采用Java的NIO实现的,而并不是是AIO,主要缘由以下:
(1)AIO在linux上底层实现仍使用EPOLL,与NIO相同,所以在性能上没有明显的优点
(2)Windows的AIO底层实现良好,但Netty的开发者并无把Windows做为主要使用平台,因此优化考虑Linux
本文主要介绍了Java里面IO模型的演变和发展,这也是Java在服务端领域大放异彩的一个重要缘由,了解这些知识以后,咱们再去学习高性能的Netty框架,将会更加容易。