在学习 Java I/O 类库时,容易混淆 NIO、BIO、AIO 这几个概念。同时也难以区分阻塞和非阻塞、同步和异步,这篇文章主要区分这几个概念的以及分享一些个人见解。 前言 各位,下面三张脑图清楚的向大家展示了 IO。 到这里,我们先来思考一个问题:我们经常所说的“IO”的全称到底是什么? 可能很多人看到这个问题和我一样一脸懵逼,IO 的全称其实是:Input/Output的缩写。 下面就让我们来进入正题。 BIO、NIO、AIO 对比 这里只考虑两个实体(客户端、服务端)和一个事件(客户端向服务端请求数据) 同步、异步描述的是:客户端在请求数据的过程中,能否做其他事情。 阻塞、非阻塞描述的是:客户端与服务端是否从头到尾始终都有一个持续连接,以至于占用了通道,不让其他客户端成功连接。 那么 BIO、NIO、AIO 就可以简单的理解为: BIO(同步阻塞):客户端在请求数据的过程中,保持一个连接,不能做其他事情。 NIO(同步非阻塞):客户端在请求数据的过程中,不用保持一个连接,不能做其他事情。(不用保持一个连接,而是用许多个小连接,也就是轮询) AIO(异步非阻塞):客户端在请求数据的过程中,不用保持一个连接,可以做其他事情。(客户端做其他事情,数据来了等服务端来通知。) 是不是逻辑清楚了?结论下完了,接下来我们说说同步与阻塞的理解。 同步和异步 3.1 常见的误区: 假设有一个展示用户详情的需求,分两步,先调用一个 HTTP 接口拿到详情数据,然后使用适合的视图展示详情数据。 如果网速很慢,代码发起一个 HTTP 请求后,就卡住不动了,直到十几秒后才拿到 HTTP 响应,然后继续往下执行。 这个时候你问别人,刚刚代码发起的这个请求是不是一个同步请求,对方一定回答是。这是对的,它确实是。 但你要问它为什么是呢?对方一定是这样回答的,“因为发起请求后,代码就卡住不动了,直到拿到响应后才可以继续往下执行”。 我相信很多人也都是这样认为的,其实这是不对的,是把因果关系搞反了:不是因为代码卡住不动了才叫同步请求,而是因为它是同步请求所以代码才卡住不动了。 至于为什么能卡住不动,这是由操作系统和 CPU 决定的:因为内核空间里的对应函数会卡住不动,造成用户空间发起的系统调用卡住不动,继而使程序里的用户代码卡住不动了。因此卡住不动了只是同步请求的一个副作用,并不能用它来定义同步请求,那该如何定义呢? 3.2 同步 所谓同步,指的是协同步调。既然叫协同,所以至少要有 2 个以上的事物存在。协同的结果就是:多个事物不能同时进行,必须一个一个的来,上一个事物结束后,下一个事物才开始。那当一个事物正在进行时,其它事物都在干嘛呢?严格来讲这个并没有要求,但一般都是处于一种“等待”的状态,因为通常后面事物的正常进行都需要依赖前面事物的结果或前面事物正在使用的资源。 因此,可以认为,同步更希望关注的是从宏观整体来看,多个事物是一种逐个逐个的串行化关系,绝对不会出现交叉的情况。所以,自然也不太会去关注某个瞬间某个具体事物是处于一个什么状态。把这个理论应用的出神入化的非“排队”莫属。凡是在资源少需求多的场景下都会用到排队。 比如排队买火车票这件事:其实售票大厅更在意的是旅客一个一个的到窗口去买票,因为一次只能卖一张票。即使大家一窝蜂的都围上去,还是一次只能卖一张票,何必呢?挤在一起又不安全。只是有些人素质太差,非要往上挤,售票大厅迫不得已,采用排队这种形式来达到自己的目的,即一个一个的买票。至于每个旅客排队时的状态,是看手机呀还是说话呀,根本不用去在意。 除了这种由于资源导致的同步外,还存在一种由于逻辑上的先后顺序导致的同步。比如,先更新代码,然后再编译,接着再打包。这些操作由于后一步要使用上一步的结果,所以只能按照这种顺序一个一个的执行。 关于同步还需知道两个小的点: 范围,并不需要在全局范围内都去同步,只需要在某些关键的点执行同步即可。比如食堂只有一个卖饭窗口,肯定是同步的,一个人买完,下一个人再买。但吃饭的时候也是一个人吃完,下一个人才开始吃吗?当然不是啦。 粒度,并不是只有大粒度的事物才有同步,小粒度的事物也有同步。只不过小粒度的事物同步通常是天然支持的,而大粒度的事物同步往往需要手工处理。比如两个线程的同步就需要手工处理,但一个线程里的两个语句天然就是同步的。 3.3 异步 […]