一、前言
最近面试时遇到了面试官问BIO、NIO、AIO相关的问题,当时有些深入点的问题没有答上来,这里给大家解释一下这三种IO的意义与关系。
二、了解IO流
Java中的流分为两种,一种是字节流,另一种是字符流,分别由四个抽象类来表示(每种流包括输入和输出两种所以一共四个):InputStream,OutputStream,Reader,Writer。Java中其他多种多样变化的流均是由它们派生出来的。
字符流和字节流是根据处理数据的不同来区分的。字节流按照8位传输,字节流是最基本的,所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘。
- 字符流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串。读文本的时候用字符流,例如txt文件。
- 节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,而字符流就可以。读非文本文件的时候用字节流,例如mp3。
IO的方式通常分为几种,同步阻塞的BIO、同步非阻塞的NIO、异步非阻塞的AIO。
三、BIO
Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
这是一个传统的连接模式,在服务器当中的实现模式是一个连接一个线程,这样的模式很明显的一个缺陷是:由于客户端连接数与服务器线程数成正比关系,可能造成不必要的线程开销
,严重的还将导致服务器内存溢出
。这种情况可以通过线程池机制改善,但并不能从本质上消除这个弊端。
那么当出现高并发场景时该怎么办?所以就有了下面的NIO。
四、NIO
从JDK1.4
以后开始,JDK引入的新的IO模型NIO,Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
Channel(通道):通道是双向的,可读也可写,而流的读写是单向的。无论读写,通道只能和Buffer交互。因为 Buffer,通道可以异步地读写。
Buffer(缓冲区):Buffer是一个对象,它包含一些要写入或者要读出的数据
Selector(选择器):选择器用于使用单个线程处理多个通道。因此,它需要较少的线程来处理这些通道。线程之间的切换对于操作系统来说是昂贵的。 因此,为了提高系统效率选择器是有用的。
就是说加了三个功能来解决bio中的单线程一对多的问题,而服务器的实现模式是多个请求一个线程,即请求会注册到多路复用器Selector上,多路复用器轮询到连接有IO请求时才启动一个线程处理。
Java NIO: 单线程管理多个连接。
但是其维护成本高
,容易出现bug,项目大了之后消耗成本
。
五、AIO
JDK1.7
发布了NIO2.0也可以说是Nio的加强版,Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO 模型,异步 IO 的操作基于事件和回调机制。
异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
这就是真正意义上的异步非阻塞,服务器的实现模式为多个有效请求一个线程,客户端的IO请求都是由OS先完成再通知服务器应用去启动线程处理(回调)。
六、各IO模型之间的关系
- BIO:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
- NIO:同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
- AIO:异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。
场景适用:
- BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
- NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
- AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
七、总结
- BIO是一个连接一个线程。
- NIO是一个请求一个线程。
- AIO是一个有效请求一个线程。
同步:
使用同步IO时,Java自己处理IO读写。
也就是必须一件一件事做,等前一件做完了才能做下一件事
(提交请求->等待服务器处理->处理完返回 这个期间客户端浏览器不能干任何事 )
异步:
使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS,完成后OS通知Java处理(回调)。
请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕
阻塞:
使用阻塞IO时,Java调用会一直阻塞到读写完成才返回。
阻塞就是说在煮水的过程中,你不可以去干其他的事情(性能和可靠性都不好)
非阻塞:
使用非阻塞IO时,如果不能立马读写,Java调用会马上返回,当IO事件分发器通知可读写时在进行读写,不断循环直到读写完成。
非阻塞就是在同样的情况下,可以同时去干其他的事情(对于低负载、低并发的应用程序)