Java IO模型

74 阅读4分钟

1.IO前置知识

为了保证操作系统的稳定性和安全性,一个进程的地址空间划分为用户空间(User space)和内核空间(Kernel space)

我们平常运行的应用程序都是运行在用户空间的,只有内核空间才能进行系统级别的资源有关的操作,比如文件管理、进程通信、内存管理等。也就是说,我们想要进行IO操作,一定要依赖内核空间的能力。用户空间的程序不能直接访问内核空间,当想要执行IO操作时,由于没有执行这些操作的权限,只能发起系统调用请求操作系统帮忙完成。

用户进程想要执行IO操作的话,必须通过系统调用来间接访问内核空间。

平常开发过程中接触最多的是磁盘IO(读写文件)和网络IO(网络请求和响应)

2.I/O调用步骤

  • 内核等待I/O设备准备好数据
  • 内核数据从内科空间拷贝到用户空间

3.UNIX系统下的IO模型

UNIX系统下,IO模型一共有5种:同步阻塞I/O、同步非阻塞I/O、I/O多路复用、信号驱动I/O和异步I/O

同步阻塞IO模型(BIO)

同步阻塞IO模型中,应用程序发起read调用后,会一直阻塞,直到内核把数据拷贝到用户空间。

img

在客户端连接数量不高的情况下,是没问题的。但是当面对十万甚至百万级连接的时候,传统的BIO模型是无能为力的。因此,我们需要一种更高效的I/O处理模型来应对更高的并发量。

Java中的NIO于Java 1.4中引入,对用的java.nio包,提供了Channel,Selector,Buffer等抽象。NIO中的N可以理解为Non-blocking,不单纯是New,它是支持面向缓冲的,基于通道的I/O操作方法,对于高负载、高并发的(网络)应该用,应使用NIO。

Java中的NIO可以看做是I/O多路复用模型,也有很多人认为,Java中的NIO属于同步非阻塞IO模型。

同步非阻塞IO模型(NIO)

同步非阻塞IO模型中,应用程序会一直发起read调用,在内核准备数据和数据就绪的时间段内应用程序是不阻塞的,但是等待数据从内核空间拷贝到用户空间的这段时间里,线程依然是阻塞的,直到在内核把数据全部拷贝到用户空间。

相比同步阻塞IO模型,同步非阻塞IO模型确实有了很大改进,通过轮询操作,避免了一直阻塞。但是这种IO模型同样存在问题:应用程序需要不断进行I/O系统调用轮询数据是否已经准备好,这个过程十分消耗CPU资源。

img

I/O多路复用模型

线程首先发起select调用,询问内核数据是否准备就绪,等内核把数据准备好了,用户线程再发起read调用,read调用的过程(数据从内核空间->用户空间)还是阻塞的

img

目前支持IO多路复用的系统调用,有select,epoll等,select系统调用,目前几乎在所有的操作系统上都有支持。

  • select 调用:内核提供的系统调用,它支持一次查询多个系统调用的可用状态。几乎所有的操作系统都支持。
  • epoll 调用:linux 2.6内核,属于select调用的增强版本,优化了IO的执行效率。

IO多路复用模型,通过减少了无效的系统调用,减少了对CPU资源的消耗。

Java中的NIO,有一个非常重要的选择器(Selector)的概念,也可以被称为多路复用器。通过它只需要一个线程便可以管理多个客户端连接,当客户端数据到了之后,才会为其服务。

img

异步IO(AIO)

异步IO是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。

目前来说AIO的应用还不是很广泛,Netty之前也尝试使用过AIO,不过又放弃了。这是因为,Netty使用了AIO之后,在Linux系统上的性能并没有多少提升。

img

各种IO模型总结

BIO:

  • 同步阻塞IO,一个连接一个线程
  • 发起请求一直阻塞,一般通过线程池改善

NIO:

  • 同步非阻塞IO,一个请求一个线程
  • 多个连接多路复用一个线程

AIO:

  • 异步非阻塞IO,一个有效请求一个线程
  • IO请求后立即返回,操作结束后,回调通知

img