JAVA中的IO同步阻塞和NIO同步非阻塞

知识共享许可协议 版权声明:署名,允许他人基于本文进行创作,且必须基于与原先许可协议相同的许可协议分发本文 (Creative Commons

IO和NIO的区别

IO NIO
面向流 面向缓冲
阻塞IO 非阻塞IO
选择器

1.面向流与面向缓冲

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

2.阻塞与非阻塞 IO

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

3.选择器(Selector)

  • Java NIO 的选择器允许一个单独的线程来监视多个输入通道, 你可以注册多个通道使用一个选择器, 然后使用一个单独的线程来“选择”通道: 这些通道里已经有可以处理的输入, 或者选择已准备写入的通道。
  • 为了将 Channel和Selector 配合使用,必须将channel 注册到 selector 上, 通过SelectableChannel.register()方法来实现。
  • 这种选择机制,使得一个单独的线程很容易来管理多个通道。 只要 Channel 向 Selector 注册了某种特定的事件,Selector 就会监听这些事件是否会发生,一旦发生某个 便会通知对应的 Channel。 使用选择器, 借助单一线程, 就可对数量庞大的活动 I/O 通道实施监控和维护

IO同步阻塞

(1)阻塞IO

最传统的一种IO模型,即在读写数据过程中会发生阻塞现象。当用户线程发出IO请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出CPU。当数据就绪之后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用户线程才解绑block状态。典型的阻塞IO模型的例子为:data = socket.read();如果数据没有就绪,就会一直阻塞在read方法。

(2)非阻塞IO

  • 当用户线程发起一个read操作后,并不需要等待,而是马上就得到了一个结果。

    • 如果结果是一个Error时,它就知道数据还没准备好,于是它可以再次发送read操作。
    • 一旦内核中的数据准备好了,并且又再次受到了用户线程的请求,那么它马上就将数据拷贝到了用户线程,然后返回。
  • 所以事实上,在非阻塞IO模型中,用户线程需要不断地询问内核数据是否就绪,也就是说非阻塞IO模型不会交出CPU,而会一直占用CPU.


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

(3)多路复用IO[同步非阻塞NIO]

在多路复用IO模型中(实际上就说NIO),

  • 会有一个线程不断去轮询多个socket的状态,只有当socket真正有读写事件时,才真正调用实际的IO读写操作。
  • 通过一个线程就可以管理多个socket,只有在真正有socket读写事件进行时,才会使用IO资源进行实际的读写操作。
  • 因此,多路复用IO比较适合连接数比较多的情况。

(4)信号驱动IO

在信号驱动IO模型中,

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

(5)异步IO[异步非阻塞AIO]

应用发起一个IO操作以后,不等待内核IO操作的完成,等内核完成IO操作以后会通知应用程序。

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

NIO同步非阻塞

在这里插入图片描述

原理

1.缓冲区(buffer)
  • 缓冲区实际上是一个容器对象, 其实就是一个数组, 在 NIO 库中,所有数据都是用缓冲区处理的。在读取数据时, 它是直接读到缓冲区中的;
  • 在写入数据时, 它也是写入到缓冲区中的; 任何时候访问 NIO 中的数据, 都是将它放到缓冲区中。 在 NIO 中, 所有的缓冲区类型都继承于 抽象类 Buffer, 最常用的就是 ByteBuffer。
2.通道(channel)
  • 通道是一个对象, 通过它可以读取和写入数据
  • 所有数据都通过 Buffer 对象来处理。 我们永远不会将字节直接写入通道中,相反是将数据写入包含一个或者多个字节的缓冲区。 同样不会直接从通道中读取字节, 而是将数据从通道读入缓冲区, 再从缓冲区获取这个字节。
  • 流只是在一个方向上移动(一个流必须是 InputStream 或者 OutputStream 的子类, 比如 InputStream 只能进行读取操作, OutputStream 只能进行写操作), 而通道是双向的, 可以用于读、 写或者同时用于读写
3.选择器(selector)
  • Selector 的作用就是用来轮询每个注册的 Channel,一旦发现 Channel 有注册的事件发生, 便获取事件然后进行处理。
  • 用单线程处理一个 Selector, 然后通过 Selector.select()方法来获取到达事件, 在获取了到达事件之后, 就可以逐个地对这些事件进行响应处理。
  • '服务端和客户端各自维护一个管理通道的对象, 我们称之为 selector, 该对象能检测一个或多个通道(channel) 上的事件。
    • 我们以服务端为例, 如果服务端的 selector 上注册了读事件, 某时刻
      • 客户端给服务端送了一些数据, 阻塞 I/O这时会调用 read()方法阻塞地读取数据, 而 NIO 的服务端会在 selector 中添加一个读事件。
      • 服务端的处理线程会轮询地访问 selector,
      • 如果访问selector 时发现有感兴趣的事件到达, 则处理这些事件, 如果没有感兴趣的事件到达, 则处理线程会一直阻塞直到感兴趣的事件到达为止。

猜你喜欢

转载自blog.csdn.net/qq_17556191/article/details/94993281