先上结论:
同步异步和阻塞非阻塞真正的区别!!!
假设某个进程正在运行下面这段代码:
......
operatorA......;
read();
operatorB......;
operatorC......;
当进程执行完operatorA后开始进行read系统调用,而所需数据尚未准备好,这时进程有两种选择:
- 等待数据准备好,然后再运行operatorB,operatorC
- 不等待数据的准备,直接运行operatorB,operatorC
同步和异步
第1种就是同步方式,第2种就是异步方式,同步和异步描述的是进程的IO操作与其之后的各指令之间运行的先后顺序及依赖关系
先不谈同步和异步如何实现,先来看为什么有同步或者异步的需求:
当operatorB,operatorC等后续操作依赖于read读入的数据时,程序必须停留在read这一步直至数据完全准备好,否则程序会出现错误的结果,这就是所谓“同步”
而当operatorB,operatorC等后续操作与read读入的数据无关时,完全可以让当前线程继续执行下去,与后台的IO操作同时进行,这就是所谓“异步”
这里可以做一下同义词转换:同步——必须保证某种指令顺序;异步——一段代码中指令执行互相没有依赖,不需要保证顺序
阻塞和非阻塞
继续看第1种情况,也就是进程等待数据准备好再执行operatorB,operatorC的同步方式,即使是等待,也有两种不同的“等待”:
- 主动放弃CPU,在阻塞态下等待
- 占用CPU进行轮询,在运行态下等待
第1种就是阻塞方式,第2种就是非阻塞方式。也就是说:阻塞和非阻塞这一对概念是在同步的基础上才有了意义,在异步下进程是“继续执行”而不是“等待”,于是不可能会涉及“以何种姿态等待”的问题,即阻塞和非阻塞这对概念描述的问题
如果学过操作系统的朋友可以感觉到:阻塞方式就是操作系统中的“中断驱动”方式,而非阻塞方式就是操作系统中的“程序控制”方式,显然前者的优点是可以更高效地使用系统资源,而后者的优点是实时性,即保证read所需的数据准备好的一瞬间就让进程继续往下执行operatorB和operatorC,而不是接受中断后进入就绪态,等待操作系统漫长的(相对而言)和不稳定的进程调度才得以继续执行
下面是对一篇讲解 Java IO 底层原理的优秀博客所做的阅读笔记和精华摘抄,原文链接为:10分钟看懂, Java NIO 底层原理
Java程序IO的本质(或操作系统IO的本质)
- read系统调用,是把数据从内核缓冲区复制到进程缓冲区;而write系统调用,是把数据从进程缓冲区复制到内核缓冲区。这个两个系统调用,都不负责数据在内核缓冲区和磁盘之间的交换。底层的读写交换,是由操作系统kernel内核完成的。
阻塞和非阻塞
- 阻塞是指用户空间(调用线程)一直在等待,而且别的事情什么都不做;
- 非阻塞是指用户空间(调用线程)拿到状态就返回,IO操作可以干就干,不可以干,就去干的事情。
在java中,默认创建的socket都是阻塞的。
同步和异步
- 同步IO是指用户空间线程是主动发起IO请求的一方,内核空间是被动接受方。
- 异步IO则反过来,是指内核kernel是主动发起IO请求的一方,用户线程是被动接受方,用户空间线程向内核空间注册各种IO事件的回调函数,由内核去主动调用
三种IO模型
同步阻塞IO(Blocking IO)
BIO的优点:
- 程序简单,在阻塞等待数据期间,用户线程挂起。用户线程基本不会占用 CPU 资源。
BIO的缺点:
- 一般情况下,会为每个连接配套一条独立的线程,或者说一条线程维护一个连接成功的IO流的读写。在并发量小的情况下,这个没有什么问题。但是,当在高并发的场景下,需要大量的线程来维护大量的网络连接,内存、线程切换开销会非常巨大。因此,基本上,BIO模型在高并发场景下是不可用的。
注意:不要混淆这里的阻塞IO和操作系统课里学的学的IO模型,操作系统里面学的IO模型是为了节省CPU资源而设计的,概念位于现在所说的JavaIO模型的下层,对Java程序而言是不可见的,而BIO、NIO、AIO是在操作系统的IO模型的上层又发展出的概念,是Java程序可见可控的
同步非阻塞NIO(None Blocking IO)
在linux系统下,可以通过设置socket使其变为non-blocking。NIO 模型中应用程序在一旦开始IO系统调用,会出现以下两种情况:
(1)在内核缓冲区没有数据的情况下,系统调用会立即返回,返回一个调用失败的信息。
(2)在内核缓冲区有数据的情况下,是阻塞的,直到数据从内核缓冲复制到用户进程缓冲。复制完成后,系统调用返回成功,应用进程开始处理用户空间的缓存数据。
NIO的优点:
- 每次发起的 IO 系统调用,在内核的等待数据过程中可以立即返回。用户线程不会阻塞,实时性较好。
NIO的缺点:
- 需要不断的重复发起IO系统调用,这种不断的轮询,将会不断地询问内核,这将占用大量的 CPU 时间,系统资源利用率较低。
点评:NIO有点是“牺牲一切为实时性服务”
Java NIO(New IO) 不是IO模型中的NIO模型,而是另外的一种模型,叫做IO多路复用模型( IO multiplexing )
IO多路复用模型
IO多路复用模型是同步的,也是阻塞的,和BIO的区别就是一个线程可以监听成百上千个文件描述符,减轻了操作系统创建那么多个线程的负担
异步IO模型(asynchronous IO)
- 当用户线程调用了read系统调用,立刻就可以开始去做其它的事,用户线程不阻塞
- 在内核kernel的等待数据和复制数据的两个阶段,用户线程都不是block(阻塞)的
- 用户线程需要注册IO操作完成的回调函数到操作系统的内核
- 异步IO有的时候,也叫做信号驱动 IO
- 真正的异步 I/O 模型
- Windows 系统下通过 IOCP 实现了真正的异步 I/O。但是很少作为百万级以上或者说高并发应用的服务器操作系统来使用。而在 Linux 系统下,异步IO模型目前并不完善。所以在 Linux 下实现高并发网络编程时都是以 IO 复用模型模式为主。
参考资料:
IO 模型知多少 | 理论篇
10分钟看懂, Java NIO 底层原理