JavaNIO--1.JavaI/O模型

JavaI/O模型

1.阻塞和非阻塞

1.1阻塞式I/O

首先要搞清楚的是,一个I/O过程,在任何操作系统下,都分为两个过程:

  1. 应用程序在用户态下发出I/O请求(系统调用),等待内核空间数据准备完成
  2. 操作系统在内核态下去读取I/O驱动中的实际数据,并将数据复制到用户空间

可以看到首先应用进程要发出请求,此时系统会切换至内核态,去查询内核空间是否已经有读入的数据,比如TCP连接中,报文会经由网络以二进制的形式传输,如果此时去调用该Socketrecvfrom()方法,由于一个完整的包还在传输过程中,内核空间并没有完整的TCP包,所以就会出现等待数据的情况,等待数据的过程中,操作系统会阻塞调用recvfrom()方法的线程,直到数据准备好以后才会返回,这种I/O过程就是阻塞式I/O。

这里写图片描述

简而言之,阻塞式I/O是在I/O第一个阶段发生了阻塞。

网络 I/O 的情况就是等待远端数据陆续抵达;磁盘I/O的情况就是等待磁盘数据从磁盘上读取到内核态内存中。

        Socket socket = new Socket();
        socket.getInputStream().read();

在Java中,基于流的操作是阻塞式的I/O操作,在内核准备好能读取到用户空间的数据之前,read()方法会阻塞当前线程。

1.2非阻塞式I/O

非阻塞式I/O和阻塞式I/O相对,就是在I/O第一个阶段不进行阻塞。

这里写图片描述

由图片可知,应该进程调用系统的recvfrom()方法,如果内核中没有准备好数据,则直接返回一个错误码(EWOULDBLOCK)。

在应用中使用非阻塞I/O一般遵循以下流程:

  1. 首先将I/O操作设置为 NONBLOCK(非阻塞)就是告诉内核,当所请求的I/O操作无法完成时,不要将线程睡眠,而是返回一个错误码(EWOULDBLOCK) ,这样请求就不会阻塞。
  2. I/O操作函数将不断的测试数据是否已经准备好,如果没有准备好,则继续轮询执行函数,直到数据准备好为止。(有点像自旋锁的概念)
  3. 数据准备好了,从内核拷贝到用户空间。

此种I/O模型优点是线程不会阻塞,减少了线程切换带来的开销。但缺点是需要不断地轮询、重复请求,消耗了大量的 CPU 的资源。

        // NIO模拟非阻塞I/O过程,但实际应用中请千万不要这样使用 !!
        // 这段代码只是演示传统非阻塞式I/O应用,实际JavaNIO并不这样使用!

        SocketChannel socketChannel = SocketChannel.open();
        // 设置为非阻塞式I/O
        socketChannel.configureBlocking(false);
        while(true) {
            // 如果有数据准备好,则会将数据从内核空间写入Buffer
            // 否则什么也不做并返回函数
            socketChannel.read(Buffer);
        }

上面就是Java中NIO基础部分,将I/O通道设置为非阻塞式通道,然后可以通过轮询实现传统的非阻塞式I/O,但事实上Java中NIO并不是单纯地非阻塞式I/O。

2.同步和异步

有很多人认为同步就是阻塞,异步就是非阻塞,这完全是一种误解。

这里先来一通高大上的讲解:

同步与异步同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)

  • 所谓同步就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。换句话说,就是由调用者主动等待这个调用的结果。
  • 而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。

作者:严肃
链接:https://www.zhihu.com/question/19732473/answer/20851256
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2.1同步I/O

首先,上面讲述的两种I/O模型,都是同步的I/O模型

什么是同步I/O呢?只要是在应用的一个线程内,必须顺序的去执行I/O操作,而且线程等待I/O操作的完成,这就是同步的I/O,拿上面的例子说,无论是阻塞式还是非阻塞式I/O,必须要在这个线程内完成从内核空间中读数据到用户空间,或者从用户空间写数据到内核空间

所以同步I/O肯定是对它的调用者有阻塞的,因为从内核空间写入用户空间的过程,是需要时间去完成,而这个操作是在调用它的线程内完成,所以该线程需要阻塞等待。

2.2异步I/O

异步I/O在Java中只有一种模型,就是AIO实现的异步I/O

但是由于AIO不是我们讨论的重点,所以就不展开叙述。

那什么是异步I/O呢?异步I/O就是线程调用了异步I/O的方法,这个方法可以没有任何返回值,线程会继续运行,而这个方法会在操作系统的安排下运行全套的I/O操作,操作完以后,再向调用方法的线程汇报一下,工作完成了!

这里写图片描述

可以看出,异步I/O对于调用它的线程可以说是完全无阻塞的,而所有的操作都被操作系统安排的明明白白,可以大大减少程序运行中由于阻塞带来的CPU开销。

3.多路复用I/O和JavaNIO

3.1操作系统的I/O多路复用

对于操作系统来说:
虽然I/O多路复用的函数也是阻塞的,但是其与以上两种还是有不同的,I/O多路复用是阻塞在select,epoll这样的系统调用之上,而没有阻塞在真正的I/O系统调用如recvfrom之上。如图

这里写图片描述

多路复用I/O比阻塞IO并没有什么优越性,关键是能实现同时对多个IO端口进行监听。

这个说法对于操作系统来说确实没什么问题,但是对于应用程序和程序员来说,就有很大的不一样。

3.2JavaNIO

JavaNIO事实上就是基于操作系统的I/O多路复用实现的。

上文我们也提到,I/O多路复用对于程序性能会有很大的提升,是因为I/O多路复用可以使用一个线程操作多个本应该多线程并发进行的I/O。这样就减少了多线程的开销,减少了线程切的代价。

特别是对于服务器来说,如果使用阻塞式I/O操作和多线程实现的话,就会如下图,每一个建立到服务器的连接都需要一个单独的线程来完成I/O操作。

这里写图片描述

但是如果使用NIO的话,我们就能实现如下的I/O模式:

这里写图片描述

4.回顾总结

  1. Java中四种I/O模型:同步阻塞I/O,同步非阻塞I/O(NIO),多路复用I/O(NIO),异步I/O(AIO)。
  2. NIO优点:减少多线程开销,对于并发访问量大的服务器软件更有效

猜你喜欢

转载自blog.csdn.net/cringkong/article/details/80112674