Java IO模型总结

293 阅读5分钟

I/O简介

I/O是什么?

I/O(Input/Output)即输入/输出

计算机结构中的I/O

冯·诺依曼结构计算机分为5大部件:运算器、控制器、存储器、输入设备、输出设备

image.png

输入设备(如键盘、鼠标)向计算机输入数据,输出设备(如显示器)接收计算机输出的数据。

从计算机结构来看,I/O描述了计算机系统与外部设备设备通信的过程。

程序中的IO

操作系统将可访问的内存空间分为两个部分,即用户空间(User Space)和内核空间(Kernel Space)

用户空间是普通应用程序可访问的内存区域。
内存空间是操作系统内核访问的区域,独立于普通的应用程序,是受保护的内存空间。所有的系统资源管理都是在内核空间完成的,如内存管理、磁盘读写、进程通信。因此,用户进程要进行IO操作,必须通过系统调用(操作系统提供的服务接口) 间接访问内核空间。

我们接触最多的就是磁盘IO(读写文件)网络IO(网络请求和响应),这里我们主要讲网络IO。

从应用程序的视角来看的话,我们的应用程序对操作系统内核发起 IO 调用(系统调用),操作系统内核执行具体的 IO 操作。

当应用程序发起 I/O 调用后,会经历两个步骤:

  1. 内核等待数据就绪
  2. 内核将数据从内核空间拷贝至用户空间

操作系统中的IO模型

Linux (UNIX)操作系统中,共有5种IO模型,分别是:阻塞IO模型非阻塞IO模型IO复用模型信号驱动IO模型以及异步IO模型

Java中2种常见的IO模型

BIO(Blocking IO)

BIO属于同步阻塞IO模型,应用程序发起read调用后,线程会阻塞,让出CPU,直到内核把数据拷贝到用户空间

同步阻塞.png

BIO的优点:

  • 编程模型简单

BIO的缺点:

  • 性能瓶颈低
  • 资源开销大

服务端实现模式为一个连接对应一个线程,即客户端有连接请求时就需要启动一个线程进行处理。如果这个连接的客户端一直不发数据,那么服务端线程将会一直阻塞在 read 函数上不返回,也无法接受其他客户端连接。

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

NIO(Non-blocking/New IO)

NIO是从Java1.4引入的IO API,对应java.nio包,提供了Channel,Selector,Buffer等抽象概念。标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。

NIO是一种同步非阻塞的IO模型,也是IO多路复用的基础

同步非阻塞IO模型

同步非阻塞 IO 模型中,应用程序会一直发起 read 调用,如果数据未能就绪,内核会返回一个错误值,而不是阻塞的等待。直到数据准备就绪,再开始处理。

同步非阻塞 (1).png

相比于同步阻塞 IO 模型,同步非阻塞 IO 模型确实有了很大改进。通过轮询操作,避免了一直阻塞。

但是,这种 IO 模型同样存在问题:应用程序不断进行 I/O 系统调用轮询数据是否已经准备好的过程是十分消耗 CPU 资源的。

这个时候,I/O 多路复用模型 就上场了。

IO多路复用模型

IO多路复用.png

使用select函数进行IO请求与同步阻塞模型并无太大区别,,使用优势主要在于用户可以在一个线程内同时处理多个IO请求,用户可以注册多个socket,然后不断调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。

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

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

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

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

image.png

NIO的优点:

  • 性能瓶颈高
  • 资源开销小

NIO的缺点:

  • 编程模型复杂

对于高负载、高并发的(网络)应用,应使用 NIO

使用NIO != 高性能,当连接数<1000,并发程度不高或者局域网环境下NIO并没有显著的性能优势。

NIO并没有完全屏蔽平台差异,它仍然是基于各个操作系统的I/O系统实现的,差异仍然存在。使用NIO做网络编程构建事件驱动模型并不容易,陷阱重重。

推荐使用成熟的NIO框架,如Netty,MINA等。解决了很多NIO的陷阱,并屏蔽了操作系统的差异,有较好的性能和编程模型。