IO原理

BIO:阻塞同步IO

NIO:同步非阻塞IO

AIO:异步非阻塞IO

JDK1.6之前都是使用BIO和NIO,JDK1.7之后AIO(NIO 2.0)得到了应用

一、阻塞IO(BIO)

阻塞IO是最传统的一种IO,在读写数据的时候会发生阻塞现象,当用户线程发出IO请求后,内核会去检查数据是否已经准备就绪,如果数据没有就绪,就会出现阻塞现象,用户线程会交出CPU。当数据准备就绪后,内核则将数据拷贝到用户线程,并返回结果给用户线程,此时用户线程的block状态才可以解除。主要原因是 socket.accept()、 socket.read()、 socket.write() 涉及的三个主要函数都是同步阻塞的,此问题可以通过线程池等多线程方案进行优化。

在BIO阻塞模式下server端:
1 new ServerSocket(int port) 监听端口
2 serverSocket.accept() 阻塞式等待客户端的连接,有连接才返回Socket对象
3 socket.getINputStream() 获取客户端发过来的信息流
4 socket.getOutputStream() 获取输出流对象,从而写入数据返回客户端

client端:
1 newSocket(String host,int port) 建立与服务器端的连接,如果服务器没启动,报Connection refused异常
2 socket.getInputStream() 读取服务器端返回的流
3 socket.getOutputStream() 获取输出流,写入数据发送到服务器端

二、同步非阻塞IO

非阻塞IO是阻塞IO的一个升级版本,具体描述为用户线程发起IO请求,内核数据查看数据是否已经准备好,如果没有准备好则返回ERROR,用户线程收到此结果后会继续发起IO请求,直至数据内核将数据准备好后再接收到用户的IO请求后,内核将数据拷贝到用户线程,然后用户数据返回数据结果并继续处理。虽然用户线程不必再一直等待返回结果了,但是用户线程会一直持有CPU资源,导致CPU的资源利用率居高不下。模型如下:

while(true){
    data = socket.read(); if(data!= error){ 处理数据
    break;
}

}

 

三、多路复用IO

多路复用IO,与BIO不同的是,BIO每一个连接都会产生一个线程,而NIO则是先建立socket连接,而且所有的连接都连接到一个多路复用器上,NIO 主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector。在多路复用的模型中,内存中会有一个线程不断轮询监听多个socket状态,只有当socket真正存在读写事件的时候,才会真正的调用IO,因为在多路复用 IO 模型中,只需要使用一个线程就可以管理多个socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有在真正有 socket 读写事件进行时,才会使用 IO 资源,所以它大大减少了资源占用。在 Java NIO 中,是通 过 selector.select()去查询每个通道是否有到达事件,如果没有事件,则一直阻塞在那里,因此这 种方式会导致用户线程的阻塞。多路复用 IO 模式,通过一个线程就可以管理多个 socket,只有当 socket 真正有读写事件发生才会占用资源来进行实际的读写操作。因此,多路复用 IO 比较适合连 接数比较多的情况。

另外多路复用 IO 为何比非阻塞 IO 模型的效率高是因为在非阻塞 IO 中,不断地询问 socket 状态时通过用户线程去进行的,而在多路复用 IO 中,轮询每个 socket 状态是内核在进行的,这个效 率要比用户线程要高的多。

不过要注意的是,多路复用 IO 模型是通过轮询的方式来检测是否有事件到达,并且对到达的事件 逐一进行响应。因此对于多路复用 IO 模型来说,一旦事件响应体很大,那么就会导致后续的事件 迟迟得不到处理,并且会影响新的事件轮询。

四、 信号驱动 IO 模型

在信号驱动 IO 模型中,当用户线程发起一个 IO 请求操作,会给对应的 socket 注册一个信号函 数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到 信号之后,便在信号函数中调用 IO 读写操作来进行实际的 IO 请求操作。

五. 异步 IO 模型

异步 IO 模型才是最理想的 IO 模型,在异步 IO 模型中,当用户线程发起 read 操作之后,立刻就 可以开始去做其它的事。而另一方面,从内核的角度,当它受到一个 asynchronous read 之后, 它会立刻返回,说明 read 请求已经成功发起了,因此不会对用户线程产生任何 block。然后,内 核会等待数据准备完成,然后将数据拷贝到用户线程,当这一切都完成之后,内核会给用户线程 发送一个信号,告诉它 read 操作完成了。也就说用户线程完全不需要实际的整个 IO 操作是如何

进行的,只需要先发起一个请求,当接收内核返回的成功信号时表示 IO 操作已经完成,可以直接 去使用数据了。

也就说在异步 IO 模型中,IO 操作的两个阶段都不会阻塞用户线程,这两个阶段都是由内核自动完 成,然后发送一个信号告知用户线程操作已完成。用户线程中不需要再次调用 IO 函数进行具体的 读写。这点是和信号驱动模型有所不同的,在信号驱动模型中,当用户线程接收到信号表示数据 已经就绪,然后需要用户线程调用 IO 函数进行实际的读写操作;而在异步 IO 模型中,收到信号 表示 IO 操作已经完成,不需要再在用户线程中调用 IO 函数进行实际的读写操作。

六、NIO具体实现逻辑

NIO 主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector。传统 IO 基于字节流和字 符流进行操作,而 NIO 基于 Channel 和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区 中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件(比如:连接打开, 数据到达)。因此,单个线程可以监听多个数据通道。

1、NIO 的缓冲区

Java IO 面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何 地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓 存到一个缓冲区。NIO 的缓冲导向方法不同。数据读取到一个它稍后处理的缓冲区,需要时可在 缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所 有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的 数据。

 

2.NIO 的非阻塞

IO 的各种流是阻塞的。这意味着,当一个线程调用 read() 或 write()时,该线程被阻塞,直到有 一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 NIO 的非阻塞模式, 使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可 用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以 继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它 完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞 IO 的空闲时间用于在其它通道上执行 IO 操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。

 

3、Channel

首先说一下 Channel,国内大多翻译成“通道”。Channel 和 IO 中的 Stream(流)是差不多一个 等级的。只不过 Stream 是单向的,譬如:InputStream, OutputStream,而 Channel 是双向 的,既可以用来进行读操作,又可以用来进行写操作。
NIO 中的 Channel 的主要实现有:

1. FileChannel
2. DatagramChannel
3. SocketChannel
4. ServerSocketChannel

这里看名字就可以猜出个所以然来:分别可以对应文件 IO、UDP 和 TCP(Server 和 Client)。 下面演示的案例基本上就是围绕这 4 个类型的 Channel 进行陈述的。

 

4、Buffer

Buffer,故名思意,缓冲区,实际上是一个容器,是一个连续数组。Channel 提供从文件、 网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer。

上面的图描述了从一个客户端向服务端发送数据,然后服务端接收数据的过程。客户端发送 数据时,必须先将数据存入 Buffer 中,然后将 Buffer 中的内容写入通道。服务端这边接收数据必 须通过 Channel 将数据读入到 Buffer 中,然后再从 Buffer 中取出数据来处理。

在 NIO 中,Buffer 是一个顶层父类,它是一个抽象类,常用的 Buffer 的子类有: ByteBuffer、IntBuffer、 CharBuffer、 LongBuffer、 DoubleBuffer、FloatBuffer、 ShortBuffer

5. Selector

Selector 类是 NIO 的核心类,Selector 能够检测多个注册的通道上是否有事件发生,如果有事 件发生,便获取事件然后针对每个事件进行相应的响应处理。这样一来,只是用一个单线程就可 以管理多个通道,也就是管理多个连接。这样使得只有在连接真正有读写事件发生时,才会调用 函数来进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护 多个线程,并且避免了多线程之间的上下文切换导致的开销。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值