--- BIO、NIO、AIO --- #### 目录 1. 思维导图 2. 概述 3. 前置知识 4. BIO 5. NIO - Buffer - Channel - Selectors 6. AIO 7. 摘自 #### 思维导图 #### 概述 BIO(Blocking I/O),即传统的 java.io 包,它基于流模型实现,提供了我们最熟知的一些 IO 功能,比如 File 抽象、输入输出流等。交互方式是同步、阻塞的方式,也就是说,在读取输入流或者写入输出流时,在读、写动作完成之前,线程会一直阻塞在那里,它们之间的调用是可靠的线形顺序。java.io 包的好处是代码比较简单、直观,缺点则是 IO 效率和扩展性存在局限性,容易成为应用性能的瓶颈。很多时候,人们也把 java.net 下面提供的部分网络 API,比如 Socket、ServiceSocket、HttpURLConnection 也归类到同步阻塞 IO 类库,因为网络通信同样是 IO 行为。 在 Java 1.4 中引入了 NIO(New IO)框架(java.nio 包),提供了 Channel、Selector、Buffer 等新的抽象,可以构建多路复用、同步非阻塞 IO 程序,同时提供了更接近操作系统底层的高性能数据操作方式。 在 Java 7 中,NIO 有了进一步的改进,也就是 NIO2,引入了异步非阻塞 IO 方式,也有很多人叫它 AIO(Asynchronous IO)。异步 IO 操作基于事件和回调机制,可以简单理解为,应用操作直接返回,而不会阻塞在那里,当后台处理完成,操作系统会通知相应线程进行后续工作。 #### 前置知识 ##### 同步和异步 简单来说,同步是一种可靠的有序运行机制,当我们进行同步操作时,后续的任务是等待当前调用返回,才会进行下一步;而异步则相反,其他任务不需要等待当前调用返回,通过依靠事件、回调等机制来实现任务间次序关系。 ##### 阻塞和非阻塞 在进行阻塞操作时,当前线程会处于阻塞状态,无法从事其他任务,只有当条件就绪才能继续,比如 ServerSocket 新连接建立完毕,或数据读取、写入操作完成;而非阻塞则是不管 I/O 操作是否结束,直接返回,相应操作在后台继续处理。 #### BIO 同步阻塞 I/O 模式,数据的读取写入必须阻塞在一个线程内等待完成。 BIO 通信(一请求一应答)模型图如下: ![](https://i.loli.net/2019/03/03/5c7b34192cf77.png) 采用 BIO 通信模型的服务端,通常由一个独立的 Acceptor 线程负责监听客户端的连接。我们一般通过在 while(true) 循环中服务端会调用 accept() 方法等待接受客户端的连接的方式监听请求,请求一但接收到一个连接请求,就可以建立通信套接字,在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求,只能等待当前连接的客户端的操作执行完毕,不过可以通过多线程来支持多个客户端的连接,如上图所示。 如果要让 BIO 通信模型能够同时处理多个客户端请求,就必须使用多线程(只要原因是 socket.accept()、socket.read()、socket.write() 涉及的三个主要函数都是同步阻塞的),也就是说它在接收到客户端连接请求之后为每个客户端创建了一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。这就是典型的**一请求一应答**通信模型。我们可以设想一下,如果这个连接不做任何事情的话就会造成不必要的线程开销,不过可以通过线程池机制改善,线程池可以让线程的创建和回收成本相对较低。使用FixedThreadPool 可以有效的控制线程的最大数量,保证了系统有限的资源的控制。 #### NIO NIO 是一种同步非阻塞的 I/O 模型,在 Java1.4 中引入了 NIO 框架,对应 java.nio 包,提供了 Channel、Selector、Buffer 等抽象。 NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它支持面向缓冲的,基于通道的 I/O 操作方法。NIO 提供了与传统 BIO 模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞 I/O 来提升开发效率和更好的维护性;对于高负载、高并发的网络应用,应该使用 NIO 的非阻塞模式来开发。 ##### Buffer 缓冲区 BIO 面向流,而 NIO 面向缓冲区。 Buffer 是一个对象,它包含一些要写入或者要读出的数据。在 NIO 类库中加入 Buffer 对象,体现了新库与原 I/O 的一个重要区别。在面向流的 I/O 中,可以将数据直接写入或者将数据直接读到 Stream 对象中。虽然 Stream 中也有 Buffer 开头的扩展类,但是只是流的包装类,还是从流读到缓冲区,而 NIO 却是直接读到 Buffer 中进行操作。 在 NIO 库中,所有数据都是用缓冲区处理的。在读数据时,它是直接读到缓冲区中去的;在写入数据时,写入到缓冲区中。任何时候访问 NIO 中的数据,都是通过缓冲区进行操作。 最常用的缓冲区是 ByteBuffer,一个 ByteBuffer 提供了一组功能用于操作 byte 数组。除了 ByteBuffer,还有其他的一些缓冲区。事实上,每一种 Java 基本类型(除了 Boolean 类型)都对应有一种缓冲区。 ##### Channel 通道 NIO 通过 Channel 进行读写。 通道是双向的,可读也可写,而流的读写是单向的。无论读写,通道只能和 Buffer 交互,因为 Buffer,通道可以异步的读写。 ##### Selectors 选择器 NIO 有选择器,而 IO 没有。 选择器用于使用单个线程处理多个通道。因此,它需要较少的线程来处理这些通道。线程之间的切换对于操作系统来说是昂贵的,因此,为了提高系统效率选择器是很有用的。 #### AIO AIO 也就是 NIO2。在 Java 7中引入了 NIO 的改进版 NIO2,它是异步非阻塞的 IO 模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会阻塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。 AIO 是异步 IO 的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO 操作本身是同步的。 #### 摘自: [BIO、NIO、AIO 总结](https://github.com/Snailclimb/JavaGuide/blob/master/Java%E7%9B%B8%E5%85%B3/BIO%2CNIO%2CAIO%20summary.md) [Java提供了哪些IO方式? NIO如何实现多路复用?](https://time.geekbang.org/column/article/8369) [漫话:如何给女朋友解释什么是Linux的五种IO模型?](https://mp.weixin.qq.com/s?__biz=Mzg3MjA4MTExMw==&mid=2247484746&idx=1&sn=c0a7f9129d780786cabfcac0a8aa6bb7&source=41#wechat_redirect) [Java NIO浅析](https://zhuanlan.zhihu.com/p/23488863)