1、概念
Java 中的 BIO、NIO和 AIO 理解为是 Java 语言对操作系统的各种 IO 模型的封装。程序员在使用这些 API 的时候,不需要关心操作系统层面的知识,也不需要根据不同操作系统编写不同的代码。只需要使用Java的API就可以了。
在讲 BIO,NIO,AIO 之前先来回顾一下这样几个概念:同步与异步,阻塞与非阻塞。并且它们又可以排列组合成:同步阻塞、同步非阻塞、异步阻塞、异步非阻塞。
1、同步:指的是用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪。比如肚子饿了去饭店吃饭,点好菜后一直问老板,饭好了没有。
2、异步:异步指的是用户进程触发IO操作以后可以去做自己的事情,而当IO操作已经完成的时候会得到IO完成的通知,异步的特点就是通知。使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS。
对于通知调用者的三种方式,具体如下:
状态:即监听被调用者的状态(轮询),调用者需要每隔一定时间检查一次,效率会很低。
通知:当被调用者执行完成后,发出通知告知调用者,无需消耗太多性能。
回调:与通知类似,当被调用者执行完成后,会调用调用者提供的回调函数。
总结来说,同步和异步的区别:请求发出后,是否需要等待结果,才能继续执行其他操作。
3、阻塞:所谓阻塞方式的意思是指, 当试图对该文件描述符进行读写时, 如果当时没有东西可读,或者暂时不可写, 程序就进入等待状态, 直到有东西可读或者可写为止。比如我们去吃饭的时候,饭一直没好,那我们就处于阻塞的状态了。
4、非阻塞:非阻塞状态下, 如果没有东西可读, 或者不可写, 读写函数马上返回,而不会一直等待。比如我们吃饭的时候如果饭没来的时候,我们可以打打游戏,看看电视,等着饭好,而不用一直傻傻的干等着。
所以:1、同步和异步是针对应用程序和内核的交互而言的。2、阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作函数的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入函数会立即返回一个状态值。 即同步和异步是目的,而阻塞和非阻塞是实现方式****。
一个IO操作其实分成了两个步骤:发起IO请求和实际的IO操作**。**
而对于同步IO和异步IO的区别就在于实际的IO操作是否阻塞,如果实际的IO操作阻塞请求进程,那么就是同步IO。
同步和异步是针对应用程序和内核的交互而言的,同步指的是用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪,而异步是指用户进程触发IO操作以后便开始做自己的事情,而当IO操作已经完成的时候会得到IO完成的通知。
而阻塞IO和非阻塞IO的区别就在于发起IO请求是否会被阻塞,如果阻塞直到完成那么就是阻塞IO,如果不阻塞,那么就是非阻塞IO。
阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作函数的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入函数会立即返回一个状态值。
所以,IO操作可以分为3类即我们这里要说的:BIO(Blocked IO:同步阻塞)、NIO(Non-Blocked IO:同步非阻塞)、AIO(Asynchronous IO:异步非阻塞)。
Java BIO (blocking IO):同步并阻塞,在JDK1.4出来之前,我们建立网络连接的时候采用BIO模式,需要先在服务端启动一个ServerSocket,然后在客户端启动Socket来对服务端进行通信,默认情况下服务端需要对每个请求建立一堆线程等待请求,而客户端发送请求后,先咨询服务端是否有线程相应,如果没有则会一直等待或者遭到拒绝请求,如果有的话,客户端会线程会等待请求结束后才继续执行。所以如果这个连接不做任何事情会造成不必要的线程开销,不过可以通过线程池机制改善。
Java NIO (non-blocking IO):同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
Java AIO(NIO.2) (Asynchronous IO) :异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。
另外还有一种异步阻塞IO,即经典的Reactor设计模式,叫的更多的其实是IO多路复用(IO Multiplexing)。多路复用是指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select和poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)。这样在处理过多的连接时,只需要1个线程监控就绪状态,对就绪的每个连接开一个线程处理就可以了,这样需要的线程数大大减少,减少了内存开销和上下文切换的CPU开销。
举个栗子:
我们在夜市买小吃的时候就经常有这样的场景:比如我们在臭豆腐摊子那里点了份臭豆腐,但是人比较多,所以我们得排队,轮到我们才能拿到臭豆腐,这个就是同步阻塞;但是有时候我们可以在臭豆腐这个点一份臭豆腐,然后跟老板说再去买份蛋炒饭过会过来拿,但是又不知道什么时候能好,毕竟臭豆腐热乎的好吃啊,所以在等蛋炒饭的时候就过几分钟就去看看臭豆腐好了没有,这种在炒饭摊和臭豆腐摊直接来回跑的就是同步非阻塞;还有一种就是我们已经是臭豆腐摊的老顾客了就跟老板说要一份臭豆腐,先去买蛋炒饭,臭豆腐好了打电话再过去拿,这就是异步阻塞;还有一种情况就是跟老板关系太好了,老板说你去买蛋炒饭吧,等臭豆腐好了我给你送过去,这个就是异步非阻塞了。
2、应用场景
BIO、NIO、AIO应用场景分析:
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
3、结尾
这里只是简单的从概念和应用层面对java中的BIO、NIO、AIO进行解释和分析,其实这里背后的原理涉及到了很多的底层的概念,比如网络、操作系统等等,这里篇幅有限就不再过分展开说明,后面再细分进行展开。