JAVA中的IO、NIO、多路复用IO、信号驱动IO、异步IO
阻塞IO模型(Blocking I/O)
最传统的IO模型,即在读写数据的过程中会发生阻塞现象。当用户线程发出IO请求后,内核线程会先查看数据是否就绪,如果数据没有就绪,就会等待就绪,用户线程线程就会处于阻塞状态,用户线程会交出CPU。当数据准备就绪后,内核线程会将数据拷贝到用户线程,并返回结果给用户线程,用户线程才会解除block状态。
非阻塞IO模型(Non-blocking I/O)
当用户发起一个read操作后,并不需要等待,而是马上可以得到一个结果,如果结果是一个error的时候,它就知道数据还没准备好,于是它会再次发起read操作。一旦内核线程准备好了,并且再次收到用户线程的read,就会将数据copy到用户线程并返回。所以在非阻塞IO模型中,用户线程是在不断的询问内核是否就绪,并且用户线程不会交出CPU。
对于非阻塞IO有一个非常严重的问题,在while循环中,需要不断的去询问,会导致CPU占用率非常高。因此一般很少用while循环去读取数据。
多路复用IO模型(IO Multiplexing)
多路复用IO模型是目前使用比较多的模型,Java NIO 实际上就是多路复用IO。在多路复用IO模型中,只需要一个线程管理维护多个socket,系统不需要创建新的线程,只有当真正有socket读写事件时候,才会调用IO资源,所以它大大减少资源占用。在Java NIO中,通过selector.select()去查询每个管道是否有事件到达,如果没有则一直阻塞在那里。
多路复用IO模式,通过一个线程就可以就可以管理多个socket,只有当socket真正有读写事件发生才会占用资源进行实际的读写操作。因此多路复用模型比较适合连接数比较多的情况。
多路复用IO为何比非阻塞IO模型效率高?
因为在非阻塞IO模型中,不断询问socket状态的是通过用户态线程进行的,而多路复用IO中,轮询每个socket状态的是在内核中进行的,这个效率比用户态效率高的多。
注意:多路复用模型是通过轮询的方式来检测是否有事件到达,并对到达事件逐一进行响应。因此一旦事件响应体很大,那么会导致后续事件迟迟得不到处理,并且会新的事件轮询。
信号驱动IO模型(signal-driven I/O)
当用户线程发起一个IO请求操作,会给socket注册一个信号函数,然后用户线程会继续执行,当内核就绪时就会发一个信号给用户线程,用户线程收到信号后,便会在信号函数中调用IO读写操作进行实际的读写操作。
异步IO模型(asynchronous I/O)
异步IO模型是最理想的IO模型,在异步IO模型中,当用户线程发起read操作后,就立即可以去做其他的事情了。而另一方面从内核角度讲,当它收到一个asynchronous read 之后,它会立即返回,表示请求已近成功发起了,因此不会对用户线程产生任何的block。
然后当内核会等待数据准备完成,并copy到用户线程,当这一切完成后,内核会发起一个信号量到用户线程,告诉用户线程read已近完成,用户线程可以直接使用数据了。
也即是说在异步IO模型中,IO操作的两个阶段都不会阻塞用户线程,两个阶段都是由内核完成的。
和信号驱动模型的区别
信号驱动模型:当用户线程收到消息后,内核时就绪状态的,需要用户线程主动执行真正的IO。
异步IO模型:当用户线程收到消息后,内核数据是已经copy到用户线程的,用户线程直接使用就可以了。
Java NIO
NIO 主要有3大核心部分:Channel(通道)、Buffer(缓冲区)、Selector(选择区)。
传统的IO是基于字节流和字符流进行操作的。而NIO是基于channel和buffer进行操作的,数据总是通过channel(通道)读取到buffer(缓冲区)中,或者从buffer(缓冲区)写入到channel(通道)。selector用于监听读个管道的事件(比如:数据到达,连接打开)。因此一个线程可以监听多个数据通道。
NIO和传统IO最大的区别:传统IO是面向流,NIO是面向缓冲区的。
-
Channel
channel和IO中的stream(流)差不多是一个等级,只不过stream是单向的channel是双向的既可以进行读,也可以进行写。
NIO中的channel主要实现有:
1、FileChannel (IO)
2、DatagramChannel (UDP)
3、SocketChannel (TCP client)
4、ServerSocketChannel (TCP server) -
Buffer
缓存区,实际上是一个容器,是一个连续的数组。channel提供从文件,网络读取数据的通道,但是读取和写入数据必须经由buffer -
Selector(选择区)
selector类是NIO的核心类,Selecetor能够检测多个注册通道上是否有事件发生,如果有事件发生,便获取事件,然后针对每个事件进行相应的响应处理。这样一来,一个单线程就看管理多个通道,也就是多个连接。
这样使得只有当真正有事件发生的时候,才会调用函数进行读写,大大减少系统开销,不必要为每个连接建立创建线程,不用维护多个线程,避免系统上下切换线程的开销。