文章目录
- javaI/O模型
- 定义
- 具体I/O流程
- 图示
- 过程
- 讲解
- 具体模型
- I/O调用两个阶段
- 同异步
- 阻塞非阻塞
- BIO(同步并阻塞)
- NIO(同步非阻塞)
- AIO(异步非阻塞)
javaI/O模型
定义
用什么样的通道或者通信模式和架构进行数据的传输和接收,很大程度上决定了程序的性能
具体I/O流程
图示
过程
-
进程发起一个系统调用sys_read,读磁盘数据;
-
DMA将磁盘文件数据拷贝到内核空间的read缓冲区;
-
CPU把内核空间read缓冲区数据拷贝到用户空间的缓冲区;
-
进程发起一个系统调用socket_write,向网卡写数据;
-
CPU把用户空间缓冲区的数据拷贝到内核空间的socket缓冲区;
-
最后DMA把内核空间的socket缓冲区数据拷贝到网卡;
讲解
-
read系统调用发起,此时进程发生一次上下文切换,由用户态切换到内核态。
-
进程从磁盘拷贝数据完毕后,read系统调用返回,此时又从内核态切换到用户态,并把磁盘数据从内核空间拷贝到用户空间。
IO读操作完成
-
进程由用户态切换到内核态,将用户空间中的数据拷贝到内核空间中的Socket缓冲区,
-
然后将数据发送给网卡。
开始往网卡里写数据
在整个IO读写过程中进行了四次上下文切换,用户态–内核态–用户态-内核态-用户态
具体模型
I/O调用两个阶段
-
内核等待I/O准备好数据
-
内核将数据从内核拷贝到用户空间
同异步
- 同步
同步方法会一直阻塞进程,直到I/O操作结束,注意这里相当于上面的阶段1,阶段2都会阻塞调用者。其中BIO,NIO,IO多路复用,信号驱动IO,这四种IO都可以归类为同步IO(也就是说进程只能先进行当前I/O操作)
- 异步
而异步方法不会阻塞调用者进程,即使是从内核空间的缓冲区将数据拷贝到进程中这一操作也不会阻塞进程,拷贝完毕后内核会通知进程数据拷贝结束。(也就是说,进程可以在处理当前I/O时处理其他事情)
阻塞非阻塞
- 阻塞
阻塞调用会一直等待远程数据就绪再返回,即上面的阶段1会阻塞调用者,直到读取结束(也就是发送者如果不说结束的话,就不会结束)
- 非阻塞
而非阻塞无论在什么情况下都会立即返回,虽然非阻塞大部分时间不会被block,但是它仍要求进程不断地去主动询问kernel是否准备好数据,也需要进程主动地再次调用recvfrom来将数据拷贝到用户内存。(也就是说发送者就算不说结束,只要当前数据发送完就会先让当前进程去处理其他事情)
同步阻塞就是我在等待当前线程干完这件事情之前不会离开.同步非阻塞就是我在当前线程干完这件事情之前,我可以去干其他的事情,但是必须时常回来看看进度.异步非阻塞,就是我在线程干这件事情的时候,我可以去干其他事情,然后等待线程通知我说这件事情已经办完了,然后再去处理.
BIO(同步并阻塞)
实现模式
为连接一个线程,客户端有连接请求时服务器端就要启动一个线程进行处理,如果这个连接不做任何事情就会造成不必要的线程开销
具体样式
小结
-
每个Socket接收到,都会创建一个线程,线程的竞争,切换上下文影响性能
-
每个线程都会占用栈空间和cpu资源
-
并非每个socket都会进行I/O操作,无意义的线程处理(服务端会一直处于阻塞状态)
-
客户端并发访问增加时,服务端会呈现1:1的线程开销,访问量越大,系统将会线程栈溢出,线程创建失败,最终导致进程宕机或僵死,从而不能对外提供服务
伪异步I/O编程
方法
采用线程池和任务队列可以实现伪异步I/O编程(相当于多个同步线程实现了一个异步线程的功能)
原理
当客户端接入时,将客户端的Socket封装成一个Task( 该任务实现java.lang.Runable线程任务接口)交给后端的线程池中进行处理。JDK 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。
模式
问题
伪异步I/O通信框架采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。不过因为它的底层仍然是同步阻塞的BIO模型,因此无法从根本上解决问题。
如果单个消息处理缓慢,或者服务器线程池所有线程都被阻塞,那么后续socket的I/O消息都会在消息队列中排队,新的socket请求会被拒绝,客户端会发生大量连接超时
NIO(同步非阻塞)
概念
提供了 Channel , Selector,Buffer 等抽象。NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它支持面向缓冲的,基于通道的 I/O 操作方法。 对于高负载、高并发的(网络)情况下,应使用 NIO 。(相当于当前线程在没有事情做的时候,可以被调用去做其他事情)
模式
三大核心
-
channel(通道)
-
功能
NIO 通过Channel(通道)对缓冲区进行读写。
-
操作
-
特点
通道是双向的,可读也可写
-
-
Buffer(缓冲区)
-
功能
Buffer是一个对象,它包含一些要写入或者要读出的数据。
-
操作
在NIO厍中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的; 在写入数据时,写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。
-
特点
NIO的缓冲区是直接缓冲:是os和jvm之间直接通过物理内存映射文件开辟的一块存储区域,像访问内存一样访问,所以速度快,但存在不安全和占用cpu的缺点,如果文件过大容易造成cpu卡死。
- Selector(选择器)
-
功能
选择器用于使用单个线程处理多个通道。(是非阻塞式I/O的核心)
-
操作
-
特点
Select可以检测多个注册的通道上是否有事件发生,如果有事件发生,以便获取事件然后进行相应的处理(管理多个连接和请求)
只有在连接或者通信,有真正的读写事件发生时才会进行读写操作,大大减少了系统开销,并且不比为每一个连接都创建一个线程,不用去维护多个线程
避免了多线程之间的上下文切换导致的开销
AIO(异步非阻塞)
AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。