阻塞IO模型:最传统的IO模型之一,即在读写数据的过程中。当用户线程发送IO请求时,核心将检查数据是否准备就绪。如果没有准备就绪,它将等待数据准备就绪,用户线程将被堵塞,用户线程将交付CPU。当数据准备就绪时,内核将数据复制到用户线程,并将结果返回到用户线程,用户线程将解除block状态。阻塞IO模型的典型例子是:data = socket.read();如果数据不准备就绪,则在read方法中总是被阻塞。
非阻塞IO模型:当用户线程启动read操作时,不需要等待,而是立即得到结果。假如结果是error,就会知道数据还没有准备好,所以它可以再次发送read操作。一旦内核中的数据准备就绪,并且再次收到用户线程的请求,那么它就会立即将数据复制到用户线程中,然后返回。因此,实际上,在非阻塞IO模型中,用户线程需要不断询问核心数据是否准备就绪,即非阻塞IO不会交出CPU,而是总是占用CPU。典型的非阻塞IO模型一般如下:
while(true){data = socket.read();if(data!= error)处理数据break;}}
然而,非阻塞IO存在非常严重的问题。在while循环中,有必要不断询问核数据是否准备就绪,这将导致CPU占用率非常高。因此,while循环很少用于读取数据。
IO模型多路复用:Java NIO实际上是多路复用IO。在多路重用IO模型中,会有一个线程不断轮流训练多个socket的状态。只有当socket真正有读写事件时,才能真正调用实际的IO读写操作。因为在多路重用IO模型中,只有一个线程可以管理多个socket,系统不需要建立新的过程或线程,也不需要维护这些线程和过程,只有在真正的socket阅读和写作事件中,才会使用IO资源,因此大大降低了资源占用。在Java 在NIO中,通过selector.select()区域检查每个通道是否有事件。如果没有事件,它总是被堵塞在哪里,因此这将导致用户线程被堵塞。多路重用IO模式可以通过一个线程管理多个socket。只有当socket真正发生读写事件时,才会占用资源进行实际的读写操作。因此,多路复用IO更适合连接数较多的情况。
此外,为什么多重用IO比非阻塞IO模型更有效,因为在非阻塞IO中,通过用户线程区域进行持续的轮换训练,而在多重用IO中,每个socket状态的轮换训练都在核心中进行,远高于用户线程。
但需要注意的是,多重复用IO模型是通过轮换培训来检测是否有事件,并逐一响应事件。因此,对于多重复用IO模型,一旦事件响应体很大,就会导致后续事件无法处理,影响新的事件轮换查询。
信号驱动IO模型:在信号驱动IO模型中,当用户线程启动IO请求操作时,将注册相应的信号函数,然后用户线程将继续执行,当核心数据将向用户线程发送信号,用户线程接收信号,调用IO读写操作进行实际IO请求操作。
异步IO模型:异步IO模型是最理想的IO模型。在异步IO模型中,当用户线程启动read操作时,可以立即开始做其他事情。另一方面,从内核的角度来看,当它受到asynchronous的影响时 read之后,它会立即返回,表明read请求已经成功启动。因此,用户线程不会产生任何block。然后,内核将等待数据准备完成,然后将数据复制到用户线程。当所有这些都完成后,内核将向用户线程发送信号。它只需要先启动一个请求。当接收到内核返回的成功信号时,意味着IO操作已经完成,数据可以直接使用。
也就是说,在异步IO模型中,IO操作的两个阶段都不会阻塞用户线程。这两个阶段由核心自动完成,然后发送信号告知用户线程已完成。IO函数不需要在用户线程中再次被调用进行具体的读写。这与信号驱动模型不同。在信号驱动模型中,当用户线程接收到信号表示数据已准备就绪时,用户线程需要调用IO模糊进行实际读写操作;在异步IO模型中,收到信号表示IO操作已完成,无需在用户线程中调用IO函数进行实际读写操作。