本篇简单介绍Java中常见的IO模型:
阻塞型IO
这是最传统的一种IO模型,在读写过程中都会发生阻塞现象;典型的阻塞型IO的例子socket.read(),当用户线程发起这个读操作时,内核会查看数据是否就绪,这时用户线程会发生阻塞,直到内核准备好数据返回。非阻塞型IO
和上述的阻塞型IO相比,该IO在用户线程发起读请求时会马上获得一个结果,如果是error则表示数据没有准备好,于是需要用户线程不停的询问直到返回结果。因此用户线程会一直占用CPU导致CPU占有率比较高。多路复用IO模型
比较典型的就是Java中的NIO,通过一个线程同时管理多个socket,并且不断询问哪些有读写事件,当发现有一个socket有读写事件时,这时才真正发生读写IO操作,因此这种模型非常适合有多个连接的场景。这里对于socket状态的轮训是发生在内核中,效率相比非阻塞型IO要高很多。多路复用IO模型中仍然有排队的问题,比如几乎同时有两个socket有了读写事件,那么必须完成前面的一个,才能进行第二个,若第一个读写数据量较大,第二个仍然需要等待。信号驱动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 函数进行实际的读写操作。