理解阻塞、非阻塞、同步、异步

理解阻塞、非阻塞、同步、异步

首先说明,这些都是在特点场景下或者相对情况的词汇,OK,接下来开门见山。

阻塞

可以很直观的理解,就如节假日高速路出口收费站一样,上图片:

1629032522587_image.png

9个收费亭,同时来了一大波车,这时候同一时刻只能有9辆车在收费,剩下的车都在只能在后面排队等待,这就是现实中很直观的阻塞现象。这9个收费亭,就是一个瓶颈,或许画为这样更符合大家对瓶颈二字的理解:

1629032857375_image.png

第1张图中,高速公路源源不断的车辆到来,和第二张图的效果其实表示一样。

OK,看图明白了现象,分析一下为什么会阻塞?

  1. 数量上:
  • 到来车辆数——大量
  • 收费站数——小于等于9个

结论:在要过卡的汽车数量大于收费亭数量时,就会有阻塞现象。

  1. 速度上:
  • 到来车辆速度——快速
  • 收费站过卡速度——慢

结论:在收费站过卡速度比车辆到来的速度慢时,就会有阻塞现象。

综合起来就是:因为量差和速度差,导致阻塞现象。

思考问题,为什么会有量差?

因为有些资源是有限的,是很难避免的,高速公路出口区域的大小有限,收费亭的个数会根据合理的规划设立,即使设立了1千个收费亭,从高速路到来的汽车跑到距离最远的那个收费亭也是相当远,没有车主愿意跑那么远去收费,它就形同虚设,有效收费亭数就还是一个相对很小的数量。同时,还需要考虑成本因素。

在程序里,比如数据库连接池里的连接是有限的,比如10条连接,但1毫秒内需要做1000个查询,就会形成阻塞现象。

而速度差是客观存在的,收费亭还需要经过不断的发展,才能达到和高速公路相匹配的速度,但收费亭还有一个作用就是让高速的车辆减速下来,去匹配非高速公路的速度。

在程序里,数据库查询,需要经过网络IO和磁盘IO,相同的内容怎么都比在本机内存中直接检索出来要慢。

阻塞,其实是一个客观存在的现象,它本质上是没法绕开的。

既然绕不开,那……非阻塞又是什么?

非阻塞

还是上面的例子,车辆经过高速路收费亭,非阻塞更像是改版的ETC,车辆进高速,扫一下车牌登记一下,车辆离开高速,扫一下车牌登记一下,然后车辆离开了,开出个几百米后车主手机才收到ETC被扣费的短信,此时高速路收费才算完成。整个过程,停留的时间很短,如果车牌识别效率非常高,甚至可以把车卡的杆去掉,这样车辆就无需停留。

1629037593718_image.png

无需停留即速度与车辆到来速度相匹配,即没有阻塞现象。

那是真的没有阻塞了吗?怎么可能,只是从车的角度来看,车确实不阻塞了,但从整个收费程序来看,车辆跑出几百米后才收费成功,就表示实际上自动扣费的速度比较慢,阻塞范围缩小到了自动扣费上。

把阻塞范围缩小,缩短主体停留时间,就是非阻塞要做的事情。

到这里,先记住这个结论,先折起一小部分内容留最后总结联系上下文……

同步

下班回家到家门口的时候,开门经过以下步骤:

  1. 掏钥匙(还需要从几百把钥匙里挑选钥匙请忽略钥匙的步骤)
  2. 插入门锁孔(磁卡锁、指纹锁、人脸锁等,请积极回忆用钥匙的日子)
  3. 旋转钥匙,开门

正常来说,三个步骤是顺序依赖的,这三步骤你怎么换人分着做,都会等待前一个步骤完成。

这时候,如果没有别的事情干扰,基本上我们会一个人去完成整个开门的事情,因为换人,也需要时间。

开门的人,看作一个主体;整个开门过程,可以看作一个事务。那么:

一个主体独自完成一个事务,便可以认为这个过程是同步的。

在程序里,给员工张三发一个节日祝福短信,步骤相似:

public static void main(String[] args) {
        // 给员工张三发一个节日祝福短信,步骤相似:

        // 1. 先把员工张三的信息查找出来
        Employee employee = findEmployee("张三");

        // 2. 编辑短信:”祝张三先生节日快乐,阖家幸福!“
        String message = "祝张三" + employee.getGender() + "节日快乐,阖家幸福!";

        // 3. 调用短信发送API发送短信内容到员工的手机号码
        sendMessage(employee.getPhone(), message);
    }
  1. 先把员工张三的信息查找出来
  2. 编辑短信:”祝张三先生节日快乐,阖家幸福!“
  3. 调用短信发送API发送短信内容到员工的手机号码

整个事务都在一条线程里顺序完成,则属于同步操作。

同步的核心,是一个主体。主要看你把什么定为一个主体。

异步

接着上面,同步是一个主体做事,那么异步,就是多个主体做事。

比如开门的例子,如果把主体具体到手,右手在做开门这些步骤时,左手可能在摘下口罩,这时候两件事情都不冲突,摘下口罩后,还可以挠挠头,抓抓痒,左手可以为所欲为(左手千万别掰断右手)。

同一时刻,多个主体在做事,就属于异步。

在程序里,线程1给张三发节日祝福短信,线程2给李四发节日祝福短信,线程3给王五发,完全没有问题,为所欲为有木有。

当然,如果多个线程在做相同的事情,也可以叫并发

思考问题,什么时候建议异步?

当多个事情没有冲突,而你又有足够的资源去同时展开工作时。

比如边开门边挠头的例子,如果你的左手因为数钱导致短暂性发麻无力,只有右手可以活动,那么边开门边挠头只会让你在切换这两件事的时候花费更多的时间。

在代码里,如果想要给张三同时发出去短信和邮件,则可以使用异步的方式去实现:

public static void main(String[] args) {
        // 给员工张三发一个节日祝福短信,步骤相似:

        // 1. 先把员工张三的信息查找出来
        Employee employee = findEmployee("张三");

        // 开启线程2去发邮件
        new Thread(() -> {// 这里边的就是异步操作
            // 编辑邮件
            String mailMessage = "祝<h3>张三</h3>" + employee.getGender() + "节日快乐,阖家幸福!";
            // 发送邮件
            sendEmail(employee.getEmail(), mailMessage);
        }).start();

        // 2. 编辑短信:”祝张三先生节日快乐,阖家幸福!“
        String message = "祝张三" + employee.getGender() + "节日快乐,阖家幸福!";

        // 3. 调用短信发送API发送短信内容到员工的手机号码
        sendMessage(employee.getPhone(), message);
    }
  1. 先把员工张三的信息查找出来
  2. 线程1(main线程):编辑短信;线程2:编辑邮件
  3. 线程1(main线程):发送短信;线程2:发送邮件

线程2在start()后,main线程就可以继续往下执行了,main线程并不会等待线程2执行完成,也就是说,异步有一个特点——非阻塞

异步可以加上回调这个利器,在执行出结果时,通过回调的方式,去反馈结果,这里不展开细谈。

总结

因为部分资源有限,所以阻塞客观存在的,可以简单的理解为有排队等待的现象,就是阻塞。

非阻塞主要是把阻塞范围缩小,或者把可以延迟完成的事情异步完成,缩短主体停留时间。

最后回到收费亭的非阻塞例子,车辆在经过出高速的收费亭登记后,就让另一条线程去执行收费操作,并不影响车辆通行,等车辆行驶出几百米后,异步的线程执行完毕,短信也发到了车主的手机上。

多加一些思考就能发现,因为速度是相对的,阻塞也是相对的,收费亭A的速度慢,但是对于它自己来说,它已经是全速了,它没停过就没有阻塞,但是高速路到来B的车因为它停下来等待了,所以阻塞须有A和B相互参照,才能看出谁是瓶颈。

同步和异步,也是相对的,这取决于主体的粒度,应用服务里A有100条线程在协同完成任务X,主体为线程时,他们是异步的,但当你把整个服务A看作一个整体时,他是同步的,因为不管你内部有多少线程,你都只是完成了任务X,仅由一个主体,完成一个事务,就是同步

运用这些思维,可以很好的去理解阻塞队列、线程池、连接池等组件,以后有空再展开吧。

总结有点啰嗦了,就这样……回见~

end.

posted @ 2021-08-16 10:39  Supalle  阅读(546)  评论(0编辑  收藏  举报