>>分享Java编程技术,对《Java面向对象编程》等书籍提供技术支持 书籍支持  卫琴直播  品书摘要  在线测试  资源下载  联系我们
发表一个新主题 开启一个新投票 回复文章 您是本文章第 13489 个阅读者 刷新本主题
 * 贴子主题:   JAVA进阶之IO模型深入解析 回复文章 点赞(0)  收藏  
作者:mary    发表时间:2021-09-05 02:46:49     消息  查看  搜索  好友  邮件  复制  引用

                                                           

引言

  I/O 一直是很多Java同学难以理解的一个知识点,这篇文章将会从底层原理上带你理解I/O,让你看清I/O相关问题的本质。

1. I/O 的概念

I/O 的全称是Input/Output。虽常谈及I/O,但想必你也一时不能给出一个完整的定义。搜索了谷哥欠,发现也尽是些冗长的论述。要想厘清I/O这个概念,我们需要从不同的视角去理解它。

1.1. 从计算机结构

根据冯.诺依曼结构,计算机结构分为 5 大部分:运算器、控制器、存储器、输入设备、输出设备。其中输入是指将数据输入到计算机的设备,比如键盘鼠标;输出是指从计算机中获取数据的设备,比如显示器;以及既是输入又是输出设备,硬盘,网卡等。

     点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

     用户通过操作系统才能完成对计算机的操作。计算机启动时,第一个启动的程序是操作系统的内核,它将负责计算机的资源管理和进程的调度。换句话说:操作系统负责从输入设备读取数据并将数据写入到输出设备。

1.2. 从程序应用

根据大学里学到的操作系统相关的知识:为了保证操作系统的稳定性和安全性,一个进程的地址空间划分为 用户空间(User space) 内核空间(Kernel space )

     应用程序作为一个文件保存在磁盘中,只有加载到内存到成为一个进程才能运行。应用程序运行在计算机内存中,必然会涉及到数据交换,比如读写磁盘文件,访问数据库,调用远程API等等。但我们编写的程序并不能像操作系统内核一样直接进行I/O操作。

     从应用程序的视角来看的话,我们的应用程序对操作系统的内核发起 IO 调用(系统调用),操作系统负责的内核执行具体的 IO 操作。也就是说,我们的应用程序实际上只是发起了 IO 操作的调用而已,具体 IO 的执行是由操作系统的内核来完成的。

     但操作系统向外提供API,其由各种类型的系统调用(System Call)组成,以提供安全的访问控制。
所以应用程序要想访问内核管理的I/O,必须通过调用内核提供的系统调用(system call)进行间接访问。

     所以I/O之于应用程序来说,强调的通过 向内核发起系统调用完成对I/O的间接访问。换句话说应用程序发起的一次IO操作实际包含两个阶段:
  1. IO调用阶段:应用程序进程向内核发起系统调用
  2. IO执行阶段:内核执行IO操作并返回
    2.1. 准备数据阶段:内核等待I/O设备准备好数据
    2.2. 拷贝数据阶段:将数据从内核缓冲区拷贝到用户空间缓冲区
UNIX 系统下, IO 模型一共有 5 种: 同步阻塞 I/O 同步非阻塞 I/O I/O 多路复用 信号驱动 I/O 异步 I/O

3. BIO (Blocking I/O)

应用程序中进程在发起IO调用后至内核执行IO操作返回结果之前,若发起系统调用的线程一直处于等待状态,则此次IO操作为阻塞IO。阻塞IO简称BIO,Blocking IO。其处理流程如下图所示:

     点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

     从上图可知当用户进程发起IO系统调用后,内核从准备数据到拷贝数据到用户空间的两个阶段期间 用户调用线程选择阻塞等待数据返回。

     因此BIO带来了一个问题:如果内核数据需要耗时很久才能准备好,那么用户进程将被阻塞,浪费性能。为了提升应用的性能,虽然可以通过多线程来提升性能,但线程的创建依然会借助系统调用,同时多线程会导致频繁的线程上下文的切换,同样会影响性能。所以要想解决BIO带来的问题,我们就得看到问题的本质,那就是 阻塞二字。

4. NIO (Non-blocking/New I/O)

那解决方案自然也容易想到,将阻塞变为非阻塞,那就是用户进程在发起系统调用时指定为非阻塞,内核接收到请求后,就会立即返回,然后用户进程通过轮询的方式来拉取处理结果。也就是如下图所示:

     点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

     应用程序中进程在发起IO调用后至内核执行IO操作返回结果之前,若发起系统调用的线程不会等待而是立即返回,则此次IO操作为非阻塞IO模型。非阻塞IO简称NIO,Non-Blocking IO。

     然而,非阻塞IO虽然相对于阻塞IO大幅提升了性能,但依旧不是完美的解决方案,其依然存在性能问题,也就是频繁的轮询导致频繁的系统调用,会耗费大量的CPU资源。比如当并发很高时,假设有1000个并发,那么单位时间循环内将会有1000次系统调用去轮询执行结果,而实际上可能只有2个请求结果执行完毕,这就会有998次无效的系统调用,造成严重的性能浪费。有问题就要解决,那 NIO问题的本质就是频繁轮询导致的无效系统调用

5. IO模型之IO多路复用

解决NIO的思路就是降解无效的系统调用,如何降解呢?我们一起来看看以下几种IO多路复用的解决思路。

     点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

     O 多路复用模型中,线程首先发起 select 调用,询问内核数据是否准备就绪,等内核把数据准备好了,用户线程再发起 read 调用。read 调用的过程(数据从内核空间->用户空间)还是阻塞的。
  目前支持 IO 多路复用的系统调用,有 select,epoll 等等。select 系统调用,是目前几乎在所有的操作系统上都有支持
  • select 调用 :内核提供的系统调用,它支持一次查询多个系统调用的可用状态。几乎所有的操作系统都支持。
  • epoll 调用 :linux 2.6 内核,属于 select 调用的增强版本,优化了 IO 的执行效率。

7. AIO (Asynchronous I/O)

AIO 也就是 NIO 2。Java 7 中引入了 NIO 的改进版 NIO 2,它是异步 IO 模型。

     异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。

     点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

     目前来说 AIO 的应用还不是很广泛。Netty 之前也尝试使用过 AIO,不过又放弃了。这是因为,Netty 使用了 AIO 之后,在 Linux 系统上的性能并没有多少提升。

8. 总结

I/O 其关键点是要将应用程序的IO操作分为两个步骤来理解:IO调用和IO执行。IO调用才是应用程序干的事情,而IO执行是操作系统的工作。在IO调用时,对待操作系统IO就绪状态的不同方式,决定了其是阻塞或非阻塞模式;在IO执行时,线程或进程是否挂起等待IO执行决定了其是否为同步或异步IO。

                         

      [/url]--]

                        
----------------------------
原文链接:https://blog.51cto.com/u_7932852/3668463

程序猿的技术大观园:www.javathinker.net

网站系统异常


系统异常信息
Request URL: http://www.javathinker.net/WEB-INF/lybbs/jsp/topic.jsp?postID=3921

java.lang.NullPointerException

如果你不知道错误发生的原因,请把上面完整的信息提交给本站管理人员