Java中的IO模型介绍

160 阅读4分钟

这是我参与8月更文挑战的第15天,活动详情查看: 8月更文挑战

阻塞(Block)和非阻塞(Non-Block)

阻塞和非阻塞是进程在访问数据的时候,数据是否准备就绪的一种处理方式,当数据没有准备的时候。

  • 阻塞:往往需要等待缓冲区中的数据准备好过后才处理其他的事情,否则一直等待在那里。
  • 非阻塞:当我们的进程访问我们的数据缓冲区的时候,如果数据没有准备好则直接返回,不会等待。如果数据已经准备好,也直接返回

同步(Synchronization)和异步(Asynchronous)

同步和异步都是基于应用程序和操作系统处理 IO 事件所采用的方式

  • 同步:是应用程序要直接参与 IO 读写 的操作

  • 异步:所有的 IO 读写交给操作系统去处理,应用程序只需要等待通知

同步方式在处理 IO 事件的时候,必须阻塞在某个方法上面等待 IO 事件完成(阻塞 IO 事件或者通过轮询 IO 事件的方式),对于异步来说,所有的 IO 读写都交给了操作系统。这个时候,可以去做其他的事情,并不需要去完成真正的 IO 操作,当操作完成 IO 后,会给我们的应用程序一个通知

BIO模型

BIO是blocking I/O的简称,属于同步阻塞IO,就是服务端为每一个请求分配一个线程进行后续的处理 BIO模型.png 当客户端新来一个后,后端的线程数就增加一个,大家都支持线程的创建,销毁都是比较耗费系统资源的,频繁的创建线程会浪费大量的资源,而且当客户端连接数比较大的时候,服务端开启的线程就会越多,性能也会明显的下降,严重的话会导致OOM 并且当客户端创建后,没有后续的操作,会进行阻塞操作,也会浪费服务端的资源,因此就有了NIO模型

NIO模型

NIO是New IO 或者 non-block IO的简称,属于非阻塞IO,NIO有三大核心组件:Buffer(缓冲区)、Channel(通道)、Selector(多路复用器/选择器)

  • Buffer

    • NIO中,所有的读写操作都是基于缓冲区完成,底层是通过数组实现,比较常用的缓冲区ByteBuffer,不同的Java基本类型都有对应的缓冲区对象,例如:CharBuffer、IntBuffer、LongBuffer等
  • Channel

    • BIO中是基于Stream流来实现,而NIO则是基于Channel通道来实现,通道顾名思义是双向的,即可以读也可以写,而Stream只能基于单向操作
  • Selector

    • Selector多路复用器,会不断的轮训注册上的Channel,如果某个Channel发生读写事件,这个Channel就会从处于就绪操作,会被Selector轮询出来,,然后通过SelectionKey获取就绪Channel的集合,进⾏IO的读写操作

BIO模型 (1).png

从模型图可以看出,NIO模型优于BIO模型,主要是因为:

  • 通过多路复用器不停的轮询实现了一个线程处理多个通道,这样可以避免线程上下文切换导致系统的额外开销
  • NIO不需要进来就为每个客户端开启一个线程,只有通道发生事件时,才会进行读写操作,这样可以减少系统的开销

AIO模型

  • 从NIO可以看出Selector多路服务器在不停轮询时,如果没有事件发生,也会进行阻塞,不同的进行轮询操作,也会浪费系统的资源,这个时候就产生了AIO模型
  • AIO是asynchronous I/O的简称,是异步IO,该异步IO是需要依赖于操作系统底层的异步IO实现。客户端连接进来后,用户线程通过系统调用,告知操作系统启动某个IO操作,用户线程返回,当某个IO线程发生读写操作后,操作系统内核在整个IO操作完成后,在通知服务端,进行后续的读写操作
  • AIO做到正在的异步操作,但是需要操作系统的支持,在NOP中的路复用模型中,不停的轮询将事件通知用户线程,有用户线程自行读取数据,处理数据;而在AIO中,当用户线程收到通知时,数据已经被内核读取完毕,并放在了用户线程指定的缓冲区内,内核在IO完成后通知用户线程直接使用即可

目前AIO的不足:

  • 完成事件的注册与传递需要操作系统底层提供大量的支持工作,Windows系统通过IOCP实现了真正的异步I/O,但是我们都知道window作为服务器的比较少,作为百万件的高并发基本上没有使用Windows操作系统
  • Linux系统,对于异步IO模型起步完,所以现在基本上都采用NIO多路复用模型模式