想要搞明白IO模型,就先得搞明白“同步”与“异步”的关系。
比如说调用者去调用一个接口,这个接口要执行一些磁盘文件读写操作,或者是网络通信操作。
假设是“同步”模式,调用者必须要等待这个接口的磁盘读写或者网络通信的操作执行完毕,调用者才能返回,这就是“同步”,如下图所示:
调用者调用接口之后,直接就返回了,去干别的事儿了,也不管那个接口的磁盘读写或者是网络通信是否成功。
然后这个接口后续如果干完了自己的任务,比如写完了文件或者是什么的,会反过来通知调用者,之前你的那个调用成功了
比如说现在你要去一个柜台买很多条香烟,但是现在柜台没那么多货,他需要打电话给库房来查一下有没有足够的货。
这个时候,库房的工作人员正好去吃饭了,那现在你有两种选择:
通常是针对底层的IO操作来说的。
比如现在我们的程序想要通过网络读取数据,如果是阻塞IO模式,一旦发起请求到操作系统内核去从网络中读取数据,就会阻塞在那里,必须要等待网络中的数据到达了之后,才能从网络读取数据到内核,再从内核返回给程序,如下图。
指的就是程序发送请求给内核要从网络读取数据,但是此时网络中的数据还没到,此时不会阻塞住,内核会返回一个异常消息给程序。
程序就可以干点儿别的,然后过一会儿再来发起一次请求给内核,让内核尝试从网络读取数据。
因为如果网络中的数据还没到位,是不会阻塞住程序的,需要程序自己不断的轮询内核去尝试读取数据,所以这种IO就是非阻塞的
大家不要把“同步/异步”概念和“阻塞/非阻塞”概念混淆起来,实际上他们是两组不同的概念。
但是在Java IO模型里,两种概念之间是有一定的关联关系的 。
就是上面图里的那种阻塞IO模式,程序发起请求之后会阻塞,一直到系统内核发现网络中有数据到达了,拷贝数据给程序进程了,才能返回
就是上面图里的那种非阻塞IO模式,程序发起请求读取数据,系统内核发现网络数据还没到,就返回一个异常信息,程序不会阻塞在IO操作上,但是过一会儿还得再来发起请求给内核,直到内核发现网络数据到达了,此时就会拷贝数据给程序进程
这个下面来讲
一般很少用到,这里不说明
下面来讲
在JDK 1.4之前,主要就是同步阻塞IO模型,在Java里叫做BIO。
在Java代码里调用IO相关接口,发起IO操作之后
在JDK 1.4出来后提供了NIO,他的概念是同步非阻塞,也就是说如果你调用NIO接口去执行IO操作
但是之所以说是同步非阻塞
实际上,如果基于NIO进行网络通信,采取的就是多路复用的IO模型,这个多路复用IO模型针对的是网络通信中的IO场景来说的。
就是在基于Socket进行网络通信的时候,如果有多个客户端跟你的服务端建立了Socket连接,那你就需要维护多个Socket连接。
而多路复用IO模型,Java代码直接通过一个select函数调用,直接会进入一个同步等待状态。
这也是为什么说NIO一定是“同步”的,因为你必须在这里同步等待某个Socket连接有请求到来。
接着你就要同步等着select函数去对底层的多个 Socket 连接进行轮询,不断的查看各个 Socket 连接谁有请求到达,就可以让select函数返回,交给我们的Java程序来处理。
select函数在底层会通过非阻塞的方式轮询各个Socket,任何一个Socket如果没有数据到达,那么非阻塞的特性会立即返回一个信息。
然后select函数可以轮询下一个Socket,不会阻塞在某个Socket上,所以底层是基于这种非阻塞的模式来“监视”各个Socket谁有数据到达的。
这就是所谓的“同步非阻塞”,但是因为操作系统把上述工作都封装在一个select函数调用里了,可以对多路Socket连接同时进行监视,所以就把这种模型称之为“IO多路复用”模型。
通过这种IO多路复用的模型,就可以用一个线程,调用一个select函数,然后监视大量的客户端连接了,如下图。
JDK 7之后,又支持了AIO,也叫做NIO 2.0,他就支持异步IO模型了。
此时不需要去管这个IO是否成功了,AIO接口会直接返回,你的Java程序也会直接返回。
然后,你的Java程序就可以去干别的事儿了。大家联想一下上面说的那个异步的例子,就可以理解这里为什么叫做异步了。
因为BIO、NIO都是同步的,你发起IO请求,都必须同步等待IO操作完成。
但是这里你发起一个IO请求,直接AIO接口就返回了,你就可以干别的事儿了,纯异步的方式。
不过你需要提供一个回调函数给AIO接口,一旦底层系统内核完成了具体的IO请求,比如网络读写之类的,就会回调你提供的回调函数。
比如说你要是通过网络读取数据,那么此时AIO接口就会把操作系统异步读取到的数据交给你的回调函数。
百度、腾讯热门面试题:聊聊Unix与Java的IO模型?(含详细解析)