Java NIO(一)同步、异步、阻塞、非阻塞

目录

一、同步、异步、阻塞、非阻塞

二、同步IO & 异步IO

三、阻塞IO & 非阻塞IO

四、传统IO的不足之处


NIO的全称是non-blocking IO,也就是说这种I/O模型是非阻塞的,就这涉及到并发的问题,主要体现在同步与异步,阻塞与非阻塞。

一、同步、异步、阻塞、非阻塞

通俗来讲

以并发的思维来理解:

1、同步:当多个任务要发生时,这些任务必须逐个地进行,一个任务的执行会导致整个流程的暂时等待,这些事件不是并发地执行的;

2、异步:当多个任务要发生时,这些任务可以并发地执行,一个任务的执行不会导致整个流程的暂时等待。

3、阻塞:当某个任务在执行过程中发出一个请求操作,但是由于该请求操作需要的条件不满足,那么就会一直在那等待,直至条件满足;

4、非阻塞:当某个任务在执行过程中发出一个请求操作,如果该请求操作需要的条件不满足,会立即返回一个标志信息告知条件不满足,不会一直处于等待状态。

以I/O模型的思维来理解:

1、同步:API调用返回时调用者就知道操作的结果如何了(实际读取/写入了多少字节)。

2、异步:相对于同步,API调用返回时调用者不知道操作的结果,后面才会回调通知结果。

3、阻塞:当无数据可读,或者不能写入所有数据时,挂起当前线程等待。

4、非阻塞:读取时,可以读多少数据就读多少然后返回,写入时,可以写入多少数据就写入多少然后返回。

二、同步IO & 异步IO

通常来说,IO操作包括:对硬盘的读写、对socket的读写以及外设的读写。

一个完整的IO读请求操作包括两个阶段:

1)查看数据是否就绪;

2)进行数据拷贝(内核将数据拷贝到用户线程)。

在《Unix网络编程》一书中对同步IO和异步IO的定义是这样的:

A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes. 
同步I/O操作会导致请求流程被阻塞,直到I/O操作完成。

An asynchronous I/O operation does not cause the requesting process to be blocked.
异步I/O操作不会导致请求流程被阻塞。

根据Oracle官网的文档,同步异步的划分标准是“调用者是否需要等待I/O操作完成”,这个“等待I/O操作完成”的意思不是指一定要读取到数据或者说写入所有数据,而是指真正进行I/O操作时,比如数据在TCP/IP协议栈缓冲区和JVM缓冲区之间传输的这段时间,调用者是否要等待。

对于同步IO,当用户发出IO请求操作之后,如果数据没有就绪,需要通过用户线程或者内核不断地去轮询数据是否就绪,当数据就绪时,再将数据从内核拷贝到用户线程;

对于异步IO,只有IO请求操作的发出是由用户线程来进行的,IO操作的两个阶段都是由内核自动完成,然后发送通知告知用户线程IO操作已经完成。也就是说在异步IO中,不会对用户线程产生任何阻塞。

所以数据拷贝阶段是由用户线程完成还是内核完成便是同步IO和异步IO 区别的关键所在。也就是说异步IO必须要有操作系统的底层支持。

三、阻塞IO & 非阻塞IO

而阻塞与非阻塞体现在上述的第一阶段:查看数据是否就绪

对于阻塞IO:如果数据没有就绪,则会一直在那等待,直到数据就绪;

对于非阻塞IO:如果数据没有就绪,则会返回一个标志信息告知用户线程当前要读的数据没有就绪。当数据就绪之后,便将数据拷贝到用户线程,这样才完成了一个完整的IO读请求操作,

Java中传统的IO都是阻塞IO,比如通过socket来读数据,调用read()方法之后,如果数据没有就绪,当前线程就会一直阻塞在read方法调用那里,直到有数据才返回;而如果是非阻塞IO的话,当数据没有就绪,read()方法应该返回一个标志信息,告知当前线程数据没有就绪,而不是一直在那里等待。

read() 和 write() 方法都是同步I/O,同步I/O又分为阻塞和非阻塞两种模式,如果是非阻塞模式,检测到无数据可读时,直接就返回了,并没有真正执行I/O操作。

总结:Java中实际上有同步阻塞I/O、同步非阻塞I/O(NIO)与异步I/O三种机制(JDK 1.7才开始引入异步 I/O,那称之为NIO.2)。

四、传统IO的不足之处

传统 I/O 是阻塞式I/O,主要问题是系统资源的浪费。比如我们为了读取一个TCP连接的数据,调用 InputStream 的 read() 方法,这会使当前线程被挂起,直到有数据到达才被唤醒,那该线程在数据到达这段时间内,占用着内存资源(存储线程栈)却无所作为。为了读取其他连接的数据,我们不得不启动另外的线程。在并发连接数量不多的时候,这可能没什么问题,然而当连接数量达到一定规模,内存资源会被大量线程消耗殆尽。另一方面,线程切换需要更改处理器的状态,比如程序计数器、寄存器的值,因此非常频繁的在大量线程之间切换,同样是一种资源浪费。


参考资料

https://blog.csdn.net/javaxuexi123/article/details/81910644

https://www.cnblogs.com/dolphin0520/p/3916526.html

猜你喜欢

转载自blog.csdn.net/qq_39192827/article/details/86558668