想理解Java的IO,不要从操作系统开始说起的都是耍流氓

Java网络IO涵盖的知识体系很广泛,本文将简单介绍Java网络IO的相关知识

从操作系统开始

为了保护操作系统的安全,会将内存分为用户空间和内核空间两个部分。如果用户想要操作内核空间的数据,则需要把数据从内核空间拷贝到用户空间

举个栗子,如果服务器收到了从客户端过来的请求,并且想要进行处理,那么需要经过这几个步骤:

想理解Java的IO,不要从操作系统开始说起的都是耍流氓

因此我们可以将服务器接收消息理解为两个阶段:

在操作系统中的IO

在此以Linux操作系统为例。Linux是一个将所有的外部设备都看作是文件来操作的操作系统,在它看来:everything is a file,那么我们就把对于外部设备的操作都看作是对文件进行操作。而且我们对一个文件进行读写,都需要通过调用内核提供的系统调用。

而在Linux中,一个基本的IO会涉及到两个系统对象:一个是调用这个IO的进程对象(用户进程),另一个是系统内核。也就是说,当一个read操作发生时,将会经历这些阶段:

想理解Java的IO,不要从操作系统开始说起的都是耍流氓

在此期间会发生几种IO操作:

可能会有读者觉得,怎么同步IO、异步IO和阻塞IO、非阻塞IO的操作好相似,为什么要它们都分出来呢?笔者认为,这同步、异步和阻塞、非阻塞是从不同角度来看待问题的

同步与异步

同步与异步主要是从消息通知的角度来说的。

同步就是当一个任务A的完成需要依赖另一个任务B时,只有等到B任务完成后,A才能成功地进行,这是一种可靠的任务队列。要么都成功,要么都失败,两个任务的状态可以保持一致。

异步是不需要等待任务B完成,只是通知任务B要完成什么工作,任务A也立即执行,只要任务A自己执行完了那么整个任务就算完成了。至于任务B最终是否真正完成,A任务无法确定,所以这是不可靠的一种任务队列

举个栗子,假如小J要去银行柜台办事,拿号排队。如果他只盯着号码提示牌,还时不时问是否到他了,这就是同步;如果他拿了号之后就去打电话了,等到排到他的时候柜员通知他去办理业务,这就是异步。他们之间的区别就在于,等待消息通知的方式不同。

阻塞与非阻塞

阻塞与非阻塞主要是从等待消息通知时的状态角度来说的。

阻塞就是指在调用结果返回之前,当前线程会被挂起,一直处于等待消息通知的状态,不能执行其他业务。只有当调用结果返回之后才能进行其他操作。

非阻塞与阻塞的概念相对应,就是指不能立即得到结果之前,该函数不会阻塞当前线程,而是会立即返回。虽然非阻塞的方式看上去可以明显提高CPU的利用率,但是也会使系统的线程切换增加,需要好好评估增加的CPU执行时间能不能步长系统的切换成本。

我们继续用上面的栗子,小J无论是在排队还是拿号等通知,如果在这个等待的过程中,小J除了等待消息通知之外就做不了其他的事情,那么该机制就是阻塞的。如果他可以一边打电话一边等待,这个状态就是非阻塞的。

同步、异步与阻塞、非阻塞

其实可能会有其他读者把同步与阻塞等同起来,实际上这两个是不同的。对于同步来说,很多时候当前线程还是在激活状态,只是逻辑上当前函数没有返回而已,此时,线程也会去处理其他的消息。也就是说,同步、阻塞其实是在消息通知机制下从不同角度对当前线程状态的描述

5.1 同步阻塞形式

这是效率最低的一种方式,拿上面的栗子来说,就是小J心无旁骛地排队,什么别的事都不做

在这里,同步与阻塞体现在:

5.2 异步阻塞形式

如果小J在银行等待办理业务的时候,领了号,这时候就采用了异步的方式去等待消息被触发(通知),等着柜员喊他的号而不是时刻盯着是不是排到他了。但是在这段时间里,他还是不能离开银行去做其他的事情,那么很显然,他被阻塞在这个等待喊号的操作上了。

在这里,异步与阻塞体现在:

5.3 同步非阻塞形式

实际上效率也是低下。小J在排队的过程中可以打电话,但是要边打电话边看看还有多久才排到他。如果将打电话和观察排队情况看成是程序中的两个操作的话,这个程序需要在这两个不同的行为之间来回切换。

在这里,同步与非阻塞体现在:

5.4 异步非阻塞形式

这是一个效率更高的模式。小J在拿号之后可以去打电话,只要等待柜员喊号就可以了,在这里打电话是等待着的事情,而通知小J办理业务是柜员的事情。

在这里,异步和非阻塞体现在:

也就是说,同步和异步仅需关注消息如何通知的机制,而阻塞和非阻塞关注的是在等待消息通知的过程中能不能去做别的事。在同步情况下,是由处理者自己去等待消息是否被触发,而异步情况下是由触发机制来通知处理者处理业务。

Linux的五种IO模型

在我们了解Linux操作系统的IO操作,以及同步与异步、阻塞与非阻塞的概念之后,我们来看看Linux系统中根据同步、异步、阻塞、非阻塞实现的五种IO模型。以Linux下的系统调用recv为例,是一个用于从套接字上接收一个消息,因为是系统调用,所以在调用的时候,会从用户空间切换到内核空间运行一段时间后,再切换回来。在默认情况下recv会等到网络数据到达并复制到用户空间或发生错误时返回。

6.1 同步阻塞IO模型

从系统调用recv到将数据从内核复制到用户空间并返回,在这段时间内进程始终阻塞。就相当于,小J想去柜台办理业务,如果柜台业务繁忙,他也要排队,直到排到他办理完业务,才能去做别的事。显然,这个IO模型是同步且阻塞的。

想理解Java的IO,不要从操作系统开始说起的都是耍流氓

6.2 同步非阻塞IO模型

在这里recv不管有没有获得到数据都返回,如果没有数据的话就过段时间再调用recv看看,如此循环。就像是小J来柜台办理业务,发现柜员休息,他离开了,过一会又过来看看营业了没,直到终于碰到柜员营业了,这才办理了业务。而小J在中间离开的时间,可以做他自己的事情。但是这个模型只有在检查无数据的时候是非阻塞的,在数据到达的时候依然要等待复制数据到用户空间(办理业务),因此它还是同步IO。

想理解Java的IO,不要从操作系统开始说起的都是耍流氓

6.3 IO复用模型

在IO复用模型中,调用recv之前会先调用select或poll,这两个系统调用都可以在内核准备好数据(网络数据已经到达内核了)时告知用户进程,它准备好了,这时候再调用recv时是一定有数据的。因此在这一模型中,进程阻塞于select或poll,而没有阻塞在recv上。就相当于,小J来银行办理业务,大堂经理告诉他现在所有柜台都有人在办理业务,等有空位再告诉他。于是小J就等啊等(select或poll调用中),过了一会儿大堂经理告诉他有柜台空出来可以办理业务了,但是具体是几号柜台,你自己找下吧,于是小J就只能挨个柜台地找。

想理解Java的IO,不要从操作系统开始说起的都是耍流氓

6.4 信号驱动IO模型

此处会通过调用sigaction注册信号函数,在内核数据准备好的时候系统就中断当前程序,执行信号函数(在这里调用recv)。相当于,小J让大堂经理在柜台有空位的时候通知他(注册信号函数),等没多久大堂经理通知他,因为他是银行的VIPPP会员,所以专门给他开了一个柜台来办理业务,小J就去特席柜台办理业务了。但即使在等待的过程中是非阻塞的,但在办理业务的过程中依然是同步的。

想理解Java的IO,不要从操作系统开始说起的都是耍流氓

6.5 异步IO模型

调用aio_read令内核把数据准备好,并且复制到用户进程空间后执行事先指定好的函数。就像是,小J交代大堂经理把业务给办理好了就通知他来验收,在这个过程中小J可以去做自己的事情。这就是真正的异步IO。

想理解Java的IO,不要从操作系统开始说起的都是耍流氓

我们可以看到,前四种模型都是属于同步IO,因为在内核数据复制到用户空间的这一过程都是阻塞的。而最后一种异步IO,通过将IO操作交给操作系统处理,当前进程不关心具体IO的实现,后来再通过回调函数,或信号量通知当前进程直接对IO返回结果进行处理。

BIO、NIO、AIO的区别

上文谈到IO的四种模式:同步阻塞IO、同步非阻塞IO、异步阻塞IO、异步非阻塞IO,在JavaIO中提供了三种模式的实现:BIO(同步阻塞IO)、NIO(同步非阻塞IO)、AIO(异步非阻塞IO)。至于这四种模式之间的区别,上文已经有较为详细的介绍了,接下来笔者将对这三种JavaIO类型之间的区别进行介绍。

结语

本文从操作系统进行文件读写入手,对同步、异步、阻塞、非阻塞以及它们组合而成的IO模式进行了介绍,还了解Linux操作系统中的五种IO模型,以及重新回到JavaIO,看待BIO、NIO、AIO之间的区别。

如果本文对你有帮助,请给一个赞吧,这会是我最大的动力~

展开阅读全文

页面更新:2024-04-28

标签:操作系统   耍流氓   线程   内核   柜台   模型   进程   事情   消息   操作   业务   通知   数据   用户   系统   科技   空间

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top