什么是IO
首先,我们要清楚什么是IO,根据冯诺依曼结构,计算机结构分为5部分:运算器、控制器、存储器、输入设备和输出设备。
输入设备和输出设备都属于外设,网卡、硬盘这种既可以属于输入设备也可以属于输出设备。
输入设备向计算机输入数据,输出设备接收计算机输出的数据。
从数据结构的时间来看的话,IO描述了计算机系统与外部设备之间通信的过程。
从应用程序的角度来看:
为了保证操作系的稳定性和安全性,一个进程的地址划分为用户空间和内核空间。
我们平时运行的程序都是在用户空间,只有内核空间才能进行系统态级别的资源有关的操作,比如文件管理,进程通信,内存管理等。也就是我们进行IO操作,一定要有依赖内核空间的能力。并且,用户空间的程序不能直接访问内核空间。
当要执行IO操作的时候,由于没有执行这些操作的权限,只能发起系统调用请求操作系统帮忙完成。
因此,用户进程想要执行IO操作的话,必须通过系统调用来简介访问内核空间。我们平常开发种接触最多的就是磁盘IO(读写文件)和网络IO(网络请求和响应) 。
从应用程序角度来说,我们应用程序对操作系统内核发起IO调用(系统调用),操作系统负责的内核执行具体的IO操作。也就是说我们的应用程序实际上只是发起了IO操作的调用而已,具体的IO执行时由擦欧总系统完成的。
当IO发起待用时,会经历两个步骤:
- 内核等待IO设备准备好数据
- 内核将数据从内核空间拷贝到用户空间
下面来介绍一些基本概念:
阻塞非阻塞 指的是在客户端
- 阻塞: 意味着 客户端提出一个请求以后,在得到回应之前,只能等待
- 非阻塞: 意味着 客户端提出一个请求以后,在得到回应之前,客户端还可以做其他事情,可以继续提其他请求
同步异步 指的是服务器端 - 同步:意味着 服务器接受一个请求后,在返回结果以前不能接受其他请求
- 异步:意味着 服务器接受一个请求后,尽管还没有返回结果,但是可以继续接受其他请求
举个例子
珂珂领着女朋友牛牛去超市购物,买了很多东西,当他走到收银员那里结账的时候,珂珂(客户端)发出了要求结账的讯息(请求),收银员(服务器)会对他这一要求进行处理。此时有可能产生多种场景
此时的同步异步,指的是收银员是否在处理收款这一请求的过程中去做了其他的事情,这也导致了收款的结果是当时告诉了珂珂,还是之后又进行了额外的通知。
而阻塞非阻塞,指的是珂珂是否在等待处理结果的过程中去做了其他的事情。
那么因此,就能得出结论:
同步和异步:关注的是被调用者是否会通过原调用通知调用者。换句话说,处理请求者是通过原调用将结果返回,还是通过其他方式将结果通知调用者。
阻塞和非阻塞:关注的是调用者是否会一直等待被调用者的通知。换句话说,发出请求者是否会在等待过程中去做别的事情。
简单的记忆方法
同步阻塞:A调用B,然后A一直等待B的返回;B执行完后通过原调用接口返回结果。
同步非阻塞:A调用B,然后A执行其他操作,隔段时间看看原调用接口是否有返回结果;B执行完后通过原调用接口返回结果。
异步阻塞:A调用B,然后A一直等待B的回调;B执行完后通过回调、状态等其他方式通知A结果。
异步非阻塞:A调用B,然后A继续做别的,不再搭理B;B执行完后通过回调、状态等其他方式通知A结果。
了解了这些,我们再说Java中有哪些常见的IO模型吧
1. 同步阻塞IO(BIO)
在BIO中,应用程序发起read调用后,会一直阻塞,知道在内核把数据拷贝到用户空间
我们经常用的IO比如:InputStream 和 OutputStream、Reader 和 Writer、
Socket 编程、File I/O。
BIO 模型的优点是简单易懂,适用于一些并发要求不高的场景。但它也有明显的缺点,主要体现在并发性能上:
- 阻塞:BIO 的阻塞特性限制了服务器的并发性能,因为每个连接都需要一个线程来处理,当连接数较多时,线程数量也会增多,占用大量系统资源。
- 扩展性差:由于每个连接都需要一个独立的线程,线程数量受限于操作系统和硬件资源,难以实现高并发。
- 高开销:线程的创建和销毁以及线程切换都会带来额外的开销,降低了系统的性能。
为了解决 BIO 模型的性能问题,后续发展了 NIO(New I/O)和 AIO(Asynchronous I/O)等模型,它们采用了非阻塞和异步的方式来提高并发性能。因此,在高并发和性能要求较高的应用中,通常会选择使用 NIO 或 AIO 模型来替代传统的 BIO 模型。
2. 同步非阻塞IO(NIO)
同步非阻塞 IO 模型中,应用程序会一直发起 read 调用,等待数据从内核空间拷贝到用户空间的这段时间里,线程依然是阻塞的,直到在内核把数据拷贝到用户空间。
NIO(New I/O,也称为 Non-blocking I/O)是 Java 中用于进行非阻塞式 I/O 操作的一种编程模型。它在 Java 1.4 版本引入,提供了一种更高效处理 I/O 操作的方式,特别适用于需要处理大量并发连接的网络应用程序。以下是常见的 NIO 组件和类:
- Channels(通道): NIO 提供了一种新的通道抽象,它是双向的,可以支持读和写操作。常用的通道包括:
- FileChannel:用于文件操作,支持文件的读写和定位操作。
- SocketChannel:用于网络套接字的读写。
- ServerSocketChannel:用于监听传入的 TCP 连接请求。
- DatagramChannel:用于 UDP 数据报的读写。
- Buffers(缓冲区): NIO 使用缓冲区来读写数据。常见的缓冲区包括:
- ByteBuffer:用于读写字节数据。
- CharBuffer:用于读写字符数据。
- ShortBuffer、IntBuffer、LongBuffer:用于读写不同数据类型的数据。
- Selectors(选择器): 选择器是 NIO 的核心组件,用于多路复用 I/O 通道。它可以监视多个通道
NIO 的主要特点是非阻塞和事件驱动,能够更高效地处理大量并发连接,适用于构建高性能的网络服务器和大规模的数据传输应用。与传统的 BIO 模型相比,NIO 更适合需要处理大量连接的场景,如 Web 服务器、聊天服务器等。
3. IO多路复用(AIO)
异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
目前来说 AIO 的应用还不是很广泛。Netty 之前也尝试使用过 AIO,不过又放弃了。这是因为,Netty 使用了 AIO 之后,在 Linux 系统上的性能并没有多少提升。