这绝对是你见过的最不正经的java IO模型完美总结了

250 阅读9分钟

前言

java诞生至今,从早期的java IO 模型到Java NIO 再到最新的java AIO。java IO发展到现在,相关的操作类也已经有几十上百个,它们共同组成了java IO完善的功能体系。小到我们日常的读取文件,大到游戏服务器的开发都有涉及,由于最近在学习Netty,而Netty则是基于java NIO开发的一个高性能的网络框架,在学习的过程中也就不可避免的开始重新审视关于java IO的一些内容,趁此机会,把常见的几种IO模型总结整理一下。本文只是宏观上解释几种不同IO模型的运行机制,如果有小伙伴对内部实际的代码细节感兴趣的话,可以私下自己查资料学习。我本人的话,最多写一篇装饰设计模式在java IO中的运用吧,毕竟自己菜,其他的也不敢乱写。

首先呢,I是input O 是ouput 这点已经不用我更多的声明了,不知道的打回去反省。IO操作呢本质上就是对内存的操作,通过IO操作我们可以实现数据的交换,比如从硬盘读取数据,用键盘打字等,虽然java 提供了字符流操作这个概念,但是在系统底层,IO操作仍然是以字节流的方式传输和处理的,字符流的出现简化了很多编码上的操作。在介绍具体的IO模型之前,我们先了解一下输入输出流的基本概念,如下:

  1. 输入/输出时,数据在通信通道中流动。所谓“数据流(stream)”指的是所有数据通信通道之中,数据的起点和终点。信息的通道就是一个数据流。只要是数据从一个地方“流"到另外一个地方,这种数据流动的通道都可以称为数据流。
  2. 输入/输出是相对于程序来说的。程序在使用数据时所扮演的角色有两个:一个是源,一一个是目的。若程序是数据流的源,即数据的提供者,这个数据流对程序来说就是一个“输出数据流"(数据从程序流出)。若程序是数据流的终点,这个数据流对程序而言就是一个“输入数据流" (数据从程序外流向程序)

IO模型

常见的IO模型主要分为以下五种,包括我们听得比较多的同步IO,阻塞IO都在这五种包括的范畴之内,它们分别是:

  • 阻塞式IO
  • 非阻塞式IO
  • 异步IO
  • 信号驱动IO
  • 多路复用IO

阻塞式IO

首先我们来理解阻塞这个词语的意思,所谓阻塞,字典上的含义是指有障碍而不能通过,比如你开车遇到堵车,前面的车就是你前行的障碍,这个情况下可以说是你在路上陷入了阻塞。关于我们程序设计中的阻塞,我个人的理解如下:

一个操作的执行依赖上一个操作所产生的结果

这种依赖是非常严重的,像是处于热恋中的情侣,离开了谁都不能活一样。比如我们常见的打印机,当一台打印机正在打印的时候,另外一个线程去请求打印机的时候,是没有办法让打印机再把打印纸吞回去打印的,这个线程唯一能做的就是等待打印机打印完当前的任务,返回OK的状态给IO线程,IO线程才可以请求打印机执行打印。同样的,在操作文件的时候,如果当前文件正在被另外一个IO线程操作,此时去请求操作文件的时候同样会陷入阻塞之中,直至文件被当前线程处理完毕。

到这里理解了阻塞IO这个概念的自然会发现它的不足,虽然在执行顺序上阻塞式IO出错的概率是很小的,但是由于阻塞的存在,特别是当一个IO线程陷入长时间的阻塞情况时,则会非常影响系统运行的效率,同时线程的创建和销毁,切换,等待操作等等操作会耗费操作系统的时间和资源,可能会导致我们的程序性能下降的问题出现。

非阻塞式IO

非阻塞式IO和阻塞式IO的最大区别就在于,非阻塞式IO并不会像阻塞式IO那样傻等着,而是隔一段时间就去看看正在操作的线程你完事了没,完事了轮到我上了。当然对于读数据操作来说,整个过程依然是阻塞的,但是因为增加了检查这一环节,使得它并不能完全称之为阻塞,因为在等待数据的过程中,它除了傻等之外,它还可以做别的事,当然也可以做检查之外的事情,但是由于它隔一段时间就要去检查是否有数据,所以也干不了什么大事,由于在实际的运行中,非阻塞式IO相对于阻塞式IO并没有带来明显的性能上的提升,也没有解决阻塞式IO的致命问题,所以在实际开发中用的也比较少。

多路复用IO

这个时候很多人就有疑问了,既然阻塞式IO和非阻塞式IO都有这么明显的缺点,难道业界就没有提供相应的解决方案吗?

答案当然是有的,那就是多路复用技术和信号驱动IO,信号驱动IO下面会讲,下面我们具体来看多路复用IO。

我个人的理解啊,多路可以理解为多个线程,复用则是重复利用。那多路复用IO究竟实现了什么骚操作了?受限于我们的操作系统的架构,再怎么骚操作也没办法实现多个线程并发读取不用等的情况,于是科学家就巧妙地想出了这么个办法,我另外开一个线程,我们叫它线程A,而另外一个线程我们称之为线程B。于是就有了下面这一段对话:

线程B来到了文件C小姐家门口,发现文件C小姐家门从里面反锁了,还挂了牌子说什么里面正在进行不可描述的&(&)%$&,线程B看自己进不去,于是想着就只能傻等了。这个时候,线程A出现了。

线程A:你这么一直等着多傻啊,对了,我是文件C小姐的助理。这样吧,这是你的号码,#008000(彩蛋提醒:颜色值),说完把一个绿色的牌子递给了线程B。然后你该干嘛干嘛去,我们小姐完事了我会打电话通知你的。

线程B:虽然感觉怪怪的,但是谢谢你啦,我先回家打游戏啦。

分析这个执行流程,我们发现有了线程A的存在,线程B不必去不停的检查是否有数据,而可以放心了去干自己的事情了。本质上大家可以将多路复用IO理解为非/阻塞式IO的加强版本,它的优势在于它可以处理大量的IO请求,用一个线程管理所有的IO请求,无需像阻塞IO和非阻塞IO一样,每个IO需要一个线程处理,提升了系统的吞吐量。

信号驱动IO

信号驱动式I/O是指进程预先告知内核,使得当某个描述符上发生某事件时,内核使用信号通知相关进程。

光看概念的确是有点晦涩难懂,突然发现上面举得那个例子还挺形象的,hhhh,可惜了,刚开始写的时候没想到,大家可以自行脑补。

过了两天,线程B再次出现在了文件C小姐的门前,好不凑巧,门依然关着,挂着那个熟悉的牌子。

线程B环顾四周,发现线程A并没有出现在这里,当线程A苦苦寻找的时候,这个时候门旁边的一个告示引起了线程A的注意:

大家好,我是线程A,现告知广大顾客朋友们,由于之前的工作实在是太枯燥了,而且挣得也少,所以我报了一个北大青熊编程培训班,我现在已经学成归来,为文件C小姐开发了全自动的客人管理通知系统,具体操作如下,扫描下列二维码,关注marson公众号,注册并成为我们的客户,点击预约,文件C小姐有空了系统会通知你。

线程A:我操,这软广。

当然实际情况下,信号驱动IO用的并不多,因为信号驱动IO底层是使用SIGIO信号,由于TCP协议在传输数据的过程中会产生大量的SIGIO信号,所以一般用在UDP上比较多。

异步IO

前面的几种IO模型本质上呢,还是属于同步IO的范畴内,因为即使多路复用IO和信号驱动IO解决了阻塞式IO的问题,但并非整个过程全部都是非阻塞的,仍然存在阻塞的环节,而异步IO它的强大之处在于,它整个过程中都是非阻塞的,包括读写数据的时候也是非阻塞的,到这里有的人可能有点迷糊了。

我们还拿线程B去文件C小姐家去吃饭这个例子。(我想的就是去吃饭啊,你们都想成什么了(滑稽))

如果是多路复用IO和信号驱动IO会是一个什么样的情况呢?

线程B收到通知,然后到了线程C小姐家,然后文件C小姐看到线程B来了,于是开始准备准备食材,做饭,注意,此刻,线程B仍然是要等待文件C小姐准备的,这个过程对于线程B来说是仍然是阻塞的。

而异步IO呢,则是线程B收到通知,然后就到了C小姐家,进屋文件C小姐:少爷,饭菜已经备好,请享用。

直接开吃,全程不需要任何等待。所以异步IO才是真正的异步,因为全程线程B没有任何等待的情况出现,不过异步IO需要操作系统内核的支持。

写在最后:

看到这里想必大家对IO模型已经有了初步的认识,后续关于NIO的笔记也会陆续发布。最后,如果想第一时间看到我发布的笔记,记得关注我哦。