Java IO

网络 I/O 模型

阻塞 I/O 模型

阻塞 I/O 模型的工作流程为:

  • 在用户线程发出 I/O 请求之后,内核会检查数据是否就绪,此时用户线程一直阻塞,等待内存数据就绪

  • 在内存数据就绪后,内核将数据复制到用户线程中,并返回 I/O 执行结果到用户线程,此时用户线程将解除阻塞状态并开始处理数据。

典型的阻塞 I/O 模型的例子为 data = socket.read() ,如果内核数据没有就绪,Socket 线程就会一直阻塞在 read() 中。

非阻塞 I/O 模型

非阻塞 I/O 模型指用户线程在发起一个 I/O 操作后,无须阻塞便可以马上得到内核返回的一个结果。如果内核返回的结果为 false,则表示内核数据还没准备好,需要稍后再发起 I/O 操作。一旦内核中的数据准备好了,并且再次收到用户线程的请求,内核就会立刻将数据复制到用户线程中并将复制的结果通知用户线程。

在非阻塞 I/O 模型中,用户线程需要不断询问内核数据是否就绪,在内存数据还未就绪时,用户线程可以处理其他任务,在内核数据就绪后可立即获取数据并进行相应的操作。

典型的非阻塞 I/O 模型一般如下:

while(true){
  data  =  socket.read();
  if(data == true){    //1:内核数据就绪
      // 获取并处理内核数据
      break;
  } else {       //2:内核数据未就绪,用户线程处理其他任务
  }
}

多路复用 I/O 模型

Java NIO 就是基于多路复用 I/O 模型实现的。

在多路复用 I/O 模型中会有一个被称为 Selector 的线程不断轮询多个 Socket 的状态,只有在 Socket 有读写事件时,才会通知用户线程进行 I/O 读写操作。

因为在多路复用 I/O 模型中只需一个线程就可以管理多个 Socket,并且在真正有 Socket 读写事件时才会使用操作系统的 I/O 资源,大大节约了系统资源。

阻塞 I/O 模型和非阻塞 I/O 模型需要为每个 Socket 都建立一个单独的线程处理该 Socket 上的数据。

多路复用 I/O 模型通过在一个 Selector 线程上以轮询方式检测在多个 Socket 上是否有事件到达,并逐个进行事件处理和响应。因此,对于多路复用 I/O 模型来说,在事件响应体(消息体)很大时,Selector 线程就会成为性能瓶颈,导致后续的事件迟迟得不到处理,影响下一轮的事件轮询。在实际应用中,在多路复用方法体内一般不建议做复杂逻辑运算,只做数据的接收和转发,将具体的业务操作转发给后面的业务线程处理。

信号驱动 I/O 模型

  1. 在信号驱动 I/O 模型中,在用户线程发起一个 I/O 请求操作时,系统会为该请求对应的 Socket 注册一个信号函数,然后用户线程可以继续执行其他业务逻辑;

  2. 在内核数据就绪时,系统会发送一个信号到用户线程,用户线程在接收到该信号后,会在信号函数中调用对应的 I/O 读写操作完成实际的 I/O 请求操作。

信号驱动模型与非阻塞模型的区别在于获取内核数据就绪状态的方式不同:

  • 信号驱动 I/O 模型是推送(push)模式

  • 非阻塞 I/O 模型是拉取(pull)模式

异步 I/O 模型

  1. 在异步 I/O 模型中,用户线程会发起一个 asynchronous read 操作到内核,内核在接收到 asynchronous read 请求后会立刻返回一个状态来说明请求是否成功发起,在此过程中用户线程不会发生任何阻塞。

  2. 接着,内核会等待数据准备完成并将数据复制到用户线程中,在数据复制完成后内核会发送一个信号到用户线程,通知用户线程 asynchronous 读操作已完成

在异步 I/O 模型中,用户线程不需要关心整个 I/O 操作是如何进行的,只需发起一个请求,在接收到内核返回的成功或失败信号时说明 I/O 操作已经完成,直接使用数据即可。

异步 I/O 模型与信号驱动 I/O 模型的区别在于:

  • 在信号驱动模型中,用户线程接收到信号便表示数据已经就绪,需要用户线程调用 I/O 函数进行实际的 I/O 读写操作,将数据读取到用户线程;

  • 在异步 I/O 模型中,用户线程接收到信号便表示 I/O 操作已经完成(数据已经被复制到用户线程),用户可以开始使用该数据了。

最后更新于