Java IO框架之BIO、NIO、AIO

我是傲骄鹿先生,沉淀、学习、分享、成长。

如果你觉得文章内容还可以的话,希望不吝您的「一键三连」,文章里面有不足的地方希望各位在评论区补充疑惑、见解以及面试中遇到的奇葩问法

目录

一、概述

二、BIO、NIO、AIO 使用场景分析

三、BIO(同步阻塞)

1、简要流程

2、服务端代码案例

3、问题分析

四、NIO(同步非阻塞)

1、NIO Buffer 的基本使用

2、NIO 和 BIO 对比

三、NIO三大核心组件关系

五、AIO

六、BIO、NIO、AIO的对比


一、概述

I/O 模型简单理解为:就是使用什么样的通道进行数据的发送和接收,很大程度上决定了程序通信的性能。Java 支持 3 种网络编程模型:BIO、NIO、AIO。

Java BIO同步并阻塞(传统阻塞型),服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不作任何事情会造成不必要的线程开销。

 Java NIO同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求会被注册到多路复用器上,多路复用器轮询到有 I/O 请求就会进行处理。

Java AIO异步非阻塞,AIO 引入了异步通道的概念,采用了 Proactor 模式,简化了程序编写,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用。 

我们可以简单通俗的进行如下的理解:

  • 同步阻塞:你到饭馆点餐,然后在那等着,还要一边喊:好了没啊!
  • 同步非阻塞:在饭馆点完餐,就去遛狗了。不过溜一会儿,就回饭馆喊一声:好了没啊!
  • 异步阻塞:遛狗的时候,接到饭馆电话,说饭做好了,让您亲自去拿。
  • 异步非阻塞:饭馆打电话说,我们知道您的位置,一会给你送过来,安心遛狗就可以了。

二、BIO、NIO、AIO 使用场景分析

  • BIO 方式适用于连接数比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4 之前唯一的选择,程序较为简单容易理解。
  • NIO 方式适用于连接数目多且连接比较短的架构,比如聊天服务器,弹幕系统,服务器间通讯等,编程比较复杂,JDK1.4 开始支持。
  • AIO 方式适用于连接数目多且连接比较长的架构,比如相册服务器,充分调用 OS 参与并发操作,变成比较复杂,JDK7 开始支持。

三、BIO(同步阻塞

Java BIO 就是传统的 Java IO 编程,其相关的类和接口在 java.io 包下。

BIO(Blocking I/O):同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时,服务器就会需要启动一个线程来进行处理。如果这个连接不作任何事情就会造成不必要的开销,可以通过线程池机制改善。

1、简要流程

  1. 服务器驱动一个 ServerSocket。
  2. 客户端启动 Socket 对服务器进行通信,默认情况下服务器端需要对每一个客户端建立一个线程进行通信。
  3. 客户端发出请求后,先咨询服务器时候否线程响应,如果没有则会等待,或者被拒绝。
  4. 如果有响应,客户端线程会等待请求结束后,再继续执行。

2、服务端代码案例

public class Server {

    public static void main(String[] args) throws IOException {
        //创建线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //创建serverSocket
        ServerSocket serverSocket = new ServerSocket(6666);
        for (; ; ) {
            System.out.println("等待连接中...");
            //监听,等待客户端连接
            Socket socket = serverSocket.accept();
            System.out.println("连接到一个客户端");
            executorService.execute(() -> handler(socket));
        }
    }

    //编写一个handler方法,和客户端通讯
    public static void handler(Socket socket) {
        byte[] bytes = new byte[1024];
        System.out.println("当前线程信息: " + Thread.currentThread().getName());
        try {
            //通过socket获取输入流
            InputStream inputStream = socket.getInputStream();
            //循环读取客户端发送的数据
            while (inputStream.read(bytes) != -1) {
                System.out.println(Thread.currentThread().getName()+ " : 发送信息为 :"+ new String(bytes, 0, bytes.length));
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            System.out.println("关闭连接");
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

3、问题分析

  1. 每个请求都需要创建独立的线程,与对应的客户端进行数据处理。
  2. 当并发数大时,需要创建大量线程来处理连接,系统资源占用较大。
  3. 连接建立后,如果当前线程暂时没有数据可读,则当前线程会一直阻塞在 Read 操作上,造成线程资源浪费。

四、NIO(同步非阻塞

Java NIO 全称 Java non-blocking IO,指的是 JDK 提供的新 API。从 JDK 1.4 开始,Java 提供了一系列改进的输入/输出的新特性,被统称为 NIO,即 New IO,是同步非阻塞的。

NIO 相关类都放在 java.nio 包下,并对原 java.io 包中很多类进行了改写。

NIO 有三大核心部分:Channel(管道)Buffer(缓冲区)Selector(选择器)

NIO 是面向缓冲区编程的。数据读取到了一个它稍微处理的缓冲区,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性,使用它可以提供非阻塞的高伸缩性网络。

Java NIO 的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用数据,如果目前没有可用数据时,则说明都不会获取,而不是保持线程阻塞,所以直到数据变为可以读取之前,该线程可以做其他事情。非阻塞写入同理。

1、NIO Buffer 的基本使用

public class BufferTest {

    public static void main(String[] args) {
        //同理对应的还有:ByteBuffer,IntBuffer,FloatBuffer,CharBuffer,ShortBuffer,DoubleBuffer,LongBuffer
        //创建一个Buffer,大小为5
        IntBuffer buffer = IntBuffer.allocate(5);
        //存放数据
        for (int i = 0; i < buffer.capacity(); i++) {
            buffer.put(i);
        }
        //切换成读模式. 读写切换
        buffer.flip();
        while (buffer.hasRemaining()) {
            System.out.println(buffer.get()); // 0 1 2 3 4
        }
    }

}

2、NIO 和 BIO 对比

  1. BIO 以流的方式处理数据,而 NIO 以块的方式处理数据,块 I/O 的效率比流 I/O 高很多。
  2. BIO 是阻塞的,而 NIO 是非阻塞的。
  3. BIO 基于字节流和字符流进行操作,而 NIO 基于 Channel(通道)和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道事件(比如连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道

三、NIO三大核心组件关系

  1. 每个 Channel 对应一个 Buffer。
  2. Selector 对应一个线程,一个线程对应多个 Channel。
  3. 该图反应了有三个 Channel 注册到该 Selector。
  4. 程序切换到那个 Channel 是由事件决定的(Event)。
  5. Selector 会根据不同的事件,在各个通道上切换。
  6. Buffer 就是一个内存块,底层是有一个数组。
  7. 数据的读取和写入是通过 Buffer,但是需要flip()切换读写模式。而 BIO 是单向的,要么输入流要么输出流。

关于Selector、Channel和Buffer详细内容

五、AIO

JDK 7 引入了 Asynchronous I/O,即 AIO。在进行 I/O 编程中,通常用到两种模式:Reactor 和 Proactor 。Java 的 NIO 就是 Reactor,当有事件触发时,服务器端得到通知,进行相应的处理。

AIO 叫做异步非阻塞的 I/O,引入了异步通道的概念,采用了 Proactor 模式,简化了程序编写,有效的请求才会启动线程,特点就是先由操作系统完成后才通知服务端程序启动线程去处理,一般用于连接数较多且连接时长较长的应用。

Reactor 与 Proactor

  • 两种 IO 多路复用方案:Reactor and Proactor。
  • Reactor 模式是基于同步 I/O 的,而 Proactor 模式是和异步 I/O 相关的。

由于 AIO 目前应用并不广泛,所以本文只是讲述 AIO 基本介绍。

六、BIO、NIO、AIO的对比

BIO g (Blocking  I/O):同步阻塞 I/O 模式,数据的读取写入必须阻塞在一个线程内等待
其完成。在活动连接数不是特别高(小于单机 1000)的情况下,这种模型是比较不错的,可
以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问
题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面
对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效
的 I/O 处理模型来应对更高的并发量。

 NIO  (New  I/O): NIO 是一种同步非阻塞的 I/O 模型,在 Java 1.4 中引入了 NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它支持面向缓冲的,基于通道的 I/O 操作方法。NIO提供了与传统 BIO 模型中的 Socket 和 ServerSocket 相对应 de 的 SocketChannel 和ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞 I/O 来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发。


AIO  (Asynchronous  I/O): AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的 IO 模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步 IO 的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO 操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。

系列文章持续更新,微信搜一搜「傲骄鹿先生 」,回复【面试】有准备的一线大厂面试资料。

猜你喜欢

转载自blog.csdn.net/cyl101816/article/details/126369949