I/O 模型与 Java
本文主要记录 Java 中 I/O 模型及相关知识点 。另,由于自身知识水平有限,如有不正之处,望各位能够谅解,欢迎批评指正!
一、I/O 基础
由于 Java 中 I/O 相关的 API ,无论是 BIO 还是 NIO,都是相对抽象的。所以我先整理一下 I/O 的基础知识,了解一下操作系统是如何处理 I/O 操作的。这一部分的内容,主要来自于『Java NIO』一书,中译版的 1.4 节:I/O 概念。
1.1 缓冲区操作
缓冲区,以及缓冲区如何工作,是所有 I/O 的基础。I/O 操作也就是 input/output,输入/输出操作,实际上就是将数据移进/移出缓冲区。进程执行 I/O 操作,归结起来,也就是向操作系统发出请求,让它将缓冲区排干(写),或者用数据将缓冲区填满(读)。这里面的读可能好理解一点,写估计就有点不太直白了。个人理解:就是将缓冲区之前写入的数据移出去,也就是发送,感觉和 Java 中 I/O 操作的 flush() 方法有点类似的感觉。
下图简单描述了数据从外部磁盘向运行中的进程的内存区域移动的过程。进程使用 read() 系统调用,要求其缓冲区被填满。内核随即向磁盘控制硬件发出命令,要求其从磁盘读取数据。磁盘控制器把数据直接写入内核内存缓冲区,当磁盘控 制器把缓冲区装满,内核即把数据从内核空间的临时缓冲区拷贝到进程执行 read() 调用时指定的缓冲区。
注意,图中用户空间和内核空间的概念。用户空间是常规进程所在区域,是非特权区域:比如,在该区域执行的代码不能直接访问硬件设备。内核空间是操作系统所在区域,是特权区域:它能与设备控制器通讯,控制着用户区域 进程的运行状态,等等。最重要的是,所有 I/O 都直接或间接通过内核空间。
为什么 I/O 操作都要直接或间接的经过内核空间?我能想到的有两点:一是安全,二是效率。书中也介绍了分散/汇聚高效的处理数据原因。
二、BIO 和 NIO
在前面演示的 I/O 过程中,大致分为两个步骤:
- 用户空间的进程发起 I/O 请求,操作系统接收到请求后,系统内核将数据从硬件中读取到内核缓冲区(准备需要的数据);
- 内核空间的系统内核将数据从内核缓冲区移动到用户空间的缓冲区,然后数据可供用户进程使用(填充缓冲区)。
显然,这两个步骤或多或少都会消耗一定的时间。那么 BIO 和 NIO 的区别就在这两个步骤中区别开了。
2.1 BIO
IO 称之为阻塞 I/O。在第一个阶段,用户进程发起 I/O 请求后,程序会处于阻塞状态,直到内核将数据准备好。然后,在第二个阶段,用户进程仍然处于阻塞状态,直到内核完成用户空间缓冲区的填充,最后用户进程开始使用数据。也就是说用户进程从发起请求的那一刻,直到数据可用,一直都处于阻塞状态。
2.2 NIO
NIO 称之为非阻塞 I/O。在第一个阶段,用户进程发起 I/O 请求后,如果内核未将数据准备好,请求会直接返回,并表明数据没有准备好。此时用户进程不会继续等待,会继续运行。一般我们都是重复的去检查内核是否将数据准备好。当数据准备好之后,进入第二阶段,发起填充缓冲区的请求,此时即便是 NIO 也会阻塞到用户空间的缓冲区填充完成。
由此,我们可以发现,BIO 和 NIO 的区别是在 I/O 操作的第一个阶段,而第二个阶段,两者都会阻塞。
在这里,我曾经有过这样的误会:既然 NIO 也是循环的去检查内核是否将数据准备好,程序本身就是要去处理即将获取的数据,那么它非阻塞的意义在哪儿呢,还不如直接阻塞的等待,何必浪费 CPU 不停的轮询呢?紧接着我看了多路复用 I/O,才算明白了 NIO 的优势。
三、多路复用 I/O
实际上 Java 中的 NIO 是多路复用 I/O 模型,真正的优势是其线程模型。而实现这个线程模型的关键在于其使用的系统调用是 select()。在介绍 select()调用前,先声明一点:Java 中的 NIO 非阻塞的特性仅适用于网络 I/O,所以下面主要针对网络 I/O 来讲。
select() 会返回当前所有数据准备就绪的 Socket,如果当前没有数据准备就绪的 Socket,select() 就会阻塞。那么只需要一个线程就能处理多个 Socket 的数据。回顾一下 BIO,当我们建立一个 Socket 连接时,这个建立连接的线程就会阻塞,直到有数据可用,也就是一个线程必须对应一个 Socket。
BIO 的劣势在于单机中线程数量是有限的,我觉得肯定比 Socket 连接数少很多吧。其次当线程数较多时,线程之间的切换会消耗掉很多的系统资源。并且当大部分的 Socket 连接的数据传输并不是很密集的时候,这些 Socket 对应的线程都处于阻塞状态,这显然是空占内存!此时就是使用 Java NIO 的绝佳机会。
可能说了那么多,大家都觉得 BIO 一无是处,Java 网络编程直接上 NIO 就 Okay!其实我觉得两者都有自己适用的环境,没有最好的,只有更合适的。
- BIO 适用场景:
普通的文件读写、Socket 连接数量较少、数据传输密集等场景。在网络编程中,一般用于客户端。 - NIO 适用场景:
普通的文件读写、Socket 连接数较多、需要考虑高并发等场景。在网络编程中,一般用于服务端。
四、AIO
AIO 称之为异步 I/O。AIO 是最理想的 I/O 处理方式。在 I/O 操作的两个阶段均不阻塞,当用户程序发起 I/O 请求时直接返回。然后系统内核完成接下的所有操作之后,会通知用户程序直接使用数据。
由于第二个阶段是由系统内核主动完成的,所以 AIO 需要操作系统底层的支持。
五、总结
下面是我在网上扒来的一张图,很好的说明了各类 I/O 模型。
关于同步与异步、阻塞与非阻塞,文末给出的参考资料值得一看。
参考资料:
- 网络IO模型 - leung - 开源中国 文中的流程图非常好,有助于理解
- [思考] 也谈同步异步I/O - SmithFox
- Java NIO:浅析I/O模型 - 海 子 - 博客园
- 怎样理解阻塞非阻塞与同步异步的区别? - 知乎 参考排名较高的回答
- IO - 同步,异步,阻塞,非阻塞 (亡羊补牢篇) - 智障大师 的专栏 - CSDN博客 文末的比喻很形象
I/O 模型与 Java的更多相关文章
- Java内存模型与Java线程实现原理
硬件的效率与一致性 基于高速缓存的存储交互很好的解决了处理器和内存的速度矛盾,但是也为计算机系统带来了更高的复杂度,因为引入了一个新问题:缓存一致性. 在多处理器系统中,每个处理器都有自己的高速缓存, ...
- 【JVM】JVM内存结构 VS Java内存模型 VS Java对象模型
原文:JVM内存结构 VS Java内存模型 VS Java对象模型 Java作为一种面向对象的,跨平台语言,其对象.内存等一直是比较难的知识点.而且很多概念的名称看起来又那么相似,很多人会傻傻分不清 ...
- 【转】JVM内存结构 VS Java内存模型 VS Java对象模型
JVM内存结构 我们都知道,Java代码是要运行在虚拟机上的,而虚拟机在执行Java程序的过程中会把所管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途. 其中有些区域随着虚拟机进程的启动而 ...
- 并发研究之Java内存模型(Java Memory Model)
Java内存模型JMM java内存模型定义 上一遍文章我们讲到了CPU缓存一致性以及内存屏障问题.那么Java作为一个跨平台的语言,它的实现要面对不同的底层硬件系统,设计一个中间层模型来屏蔽底层的硬 ...
- java内存结构JVM——java内存模型JMM——java对象模型JOM
JVM内存结构 Java代码是要运行在虚拟机上的,而虚拟机在执行Java程序的过程中会把所管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途.其中有些区域随着虚拟机进程的启动而存在,而有些区 ...
- Java网络编程和NIO详解3:IO模型与Java网络编程模型
Java网络编程和NIO详解3:IO模型与Java网络编程模型 基本概念说明 用户空间与内核空间 现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32 ...
- JAVA内存模型(Java Memory Model ,JMM)
http://blog.csdn.net/hxpjava1/article/details/55189077 JVM有主内存(Main Memory)和工作内存(Working Memory),主内存 ...
- java内存模型(Java Memory Model)
内容导航: Java内存模型 硬件存储体系结构 Java内存模型和硬件存储体系之间的桥梁: 共享对象的可见性 竞争条件 Java内存模型规定了JVM怎样与计算机存储系统(RAM)协调工作.JVM是一个 ...
- JVM内存结构 VS Java内存模型 VS Java对象模型
前面几篇文章中, 系统的学习了下JVM内存结构.Java内存模型.Java对象模型, 但是发现自己还是对这三者的概念和区别比较模糊, 傻傻分不清楚.所以就有了这篇文章, 本文主要是对这三个技术点再做一 ...
- 硬件内存模型到 Java 内存模型,这些硬核知识你知多少?
Java 内存模型跟上一篇 JVM 内存结构很像,我经常会把他们搞混,但其实它们不是一回事,而且相差还很大的,希望你没它们搞混,特别是在面试的时候,搞混了的话就会答非所问,影响你的面试成绩,当然也许你 ...
随机推荐
- 干货!一篇文章集合所有Linux基础命令
1 文件{ls -rtl # 按时间倒叙列出所有目录和文件 ll -rttouch file # 创建空白文件rm -rf 目录名 # 不提示删除非空目录(-r:递归删除 -f强制)dos2unix ...
- Promise 错误处理
Promise 是一个异步返回单个结果的函数或方法 不使用 `catch()` 时,在 `success handler` 里的错误无法被捕捉到 使用 `catch()` 时,在 `succe ...
- mybatis-generator 代码自动生成工具包
怎么用mybatis-gennerator插件自动生成mybatis所需要的dao.bean.mapper xml文件.请看↓ 1.在D盘新建一个文件夹,命名:generator(或者其他盘其他名字也 ...
- Java中的异常处理与抛出
一.异常处理 程序运行过程中出现的,导致程序无法继续运行的错误叫做异常. Java中有多种异常,所有异常的父类是Throwable,他主要有两个子类Error和Exception. Error一般是J ...
- Angular cli 发布自定义组件
建立工作空间 ng new Test --style=scss //Angular6.x及以下可以使用这个命令指定使用.scss样式表 ng new Test ...
- 使用spring:aop中修改增强方法中的参数
大家都知道,在增强方法中,使用jp.getArgs()[index]可以获取传进来的参数,但是参数传进来之后,怎么改变它的值呢? 因为jp.getArgs()[index]获取到的只是数据的备份,所以 ...
- python 全栈开发笔记 1
将自己的姓名用进制表示出来 #自己姓名的进制表示 name=input('请输入你的名字:') for i in name: print(i) # python 3 中是按字符进行循环的 bytes_ ...
- Python列表的增删改查和元祖
一.定义列表 1.names = ['mike','mark','candice','laular'] #定义列表 2.num_list = list(range(1,10)) #range生成1-1 ...
- SpringMVC云题库错题及答案汇总-2
此题目考察的是SpringMVC-注解驱动控制器,注释类型的范围: A.处理requet uri 部分的注解: @PathVariable; B.处理request header部分的注解: @Req ...
- chrome恢复默认搜索引擎为Google
管理员身份运行cmd RD /S /Q "%WinDir%\System32\GroupPolicyUsers" RD /S /Q "%WinDir%\System32\ ...