科幻电影里的超能力?那不就是并发嘛!

753b17a0297ac2443331b4d91a057e61.png

本文节选自《On Java 中文版 进阶卷》

第5章 并发编程

ON JAVA

并发的超能力

想象你是一部科幻电影中的角色。有一栋拥有1000万个房间的高楼,你的目标巧妙地藏身于其中一个房间,而你需要找到它。你进入大楼,沿着走廊往里走,走廊尽头是岔路口。

如果仅靠你一个人,你得花100辈子才能完成这个任务。

这时假设你拥有一种超能力,可以克隆一个自己的分身,然后让分身沿着岔路继续走下去,你自己则继续往前走。每次遇到岔路或者上楼的楼梯,你就重复克隆的操作。最终整个大楼的每一条走廊尽头都会有你的一个分身。

每条走廊有1000个房间,你的超能力不太够,同时最多只能克隆出50个自己。

一旦一个分身进入一个房间,他需要寻遍每一个角落。这时他启动了第二个超能力,将自己分裂成100万个纳米机器人,每个机器人都飞入或爬入某个隐蔽的角落。你不用明白这是怎么一回事儿,只要知道可以这么干就行。这些纳米机器人自行搜寻房间,完成之后就重新组装成你的分身。你忽然间就知道目标是否在这个房间里了。

我很想说:“科幻电影里的超能力?那不就是并发嘛!”每来一个新任务,就克隆一个分身,就这么简单。但问题是不论我们用哪种模型来描述这个奇迹,最终都会陷入抽象泄露的困境中。

ON JAVA

抽象泄露的困境

其中一种泄露是这样的:在理想世界中,每次克隆你都需要复制一套硬件处理器来运行新的分身。但这显然不可能做到,实际上你的机器只会有4个或8个处理器(常见情况)。你可能有更多的处理器,但在很多情况下也很可能只有1个。在抽象的相关探讨中,物理处理器的分配方式不仅会被泄露,而且甚至还能左右你的决策。

我们再来修改一下电影设定。现在,当每个分身到达一个房间门口时,他都需要敲门,并且等待开门。如果每个分身都能分配一个处理器,那就不会有什么问题。处理器挂起等待就行,直到有人开门。但是,如果只有8个处理器,却有成千上万个分身,我们并不希望处理器被闲置,仅仅因为一个分身等待开门,并由此导致阻塞。我们希望能将处理器分配给当前能干活的分身,所以需要一套将处理器在任务间切换分配的机制。

很多模型可以将处理器的实际数量对外屏蔽,并允许你伪装成有很多处理器的样子。但在某些场合下,该机制可能失效,这时就必须清楚地知道处理器的实际数量,由此才能围绕实际数量做出更优决策。

ON JAVA

一个关键的决定因素

一个关键的决定因素是处理器是否多于一个。如果只有一个处理器,那么它要承受额外的任务切换带来的性能损耗,这时并发反而会使系统变得更慢。

假设有一个负责电话应答的客服部门,客服团队人数有限,但是可能会有很多电话打进来,客服人员(相当于处理器)必须在一部电话上完成整个通话才能挂断,其他打进的电话则必须等待。

在童话故事《鞋匠和精灵》里,鞋匠要做的鞋太多了,当他睡觉时,一群精灵会来帮他做鞋,此时就变成了分布式处理。但即使有很多物理处理器(精灵),性能还是会在某些部件生产上遇到瓶颈,比如做鞋底需要的时间最长,那么这个环节就会限制鞋的整体生产速度,相应地也会迫使你优化解决方案。

因此,不同的问题会促成不同的方案设计。理想很美好,我们可以把任务分解成很多子任务并让它们“各自独立运行”,但现实则往往是另一回事,物理现实会不断打击美好的理想。

ON JAVA

麻烦不止这些

麻烦还不止这些。假设有个生产蛋糕的工厂,我们已经在某种程度上实现了将蛋糕生产的任务按人拆分,但问题还是来了。工人A即将执行将蛋糕放入包装盒的步骤,盒子也已经就绪,结果当工人A开始放入蛋糕的操作时,工人B抢先一步将另一个蛋糕放进去了,而工人A并未停止手头的动作,结果“吧唧”一下,两个蛋糕碰到了一起,形状也都破坏了。这就是导致常见的竞态条件(race condition)的“共享内存”问题,其结果取决于哪位工人会先将他的蛋糕放入盒子(典型的解决方案是使用锁机制,总有一位工人能先锁定盒子,从而避免别人的蛋糕放入)。

这种问题通常发生于同时执行的任务互相干扰时,而且常常表现得复杂微妙、难以预测,因此可以说并发是“理论上充满确定性,实际上充满不确定性”。意思是说,你总是感觉你的并发程序经过了周全的考虑,代码检测没问题,运行也正确。但现实中更常见的情况是,写出来的并发程序只是看起来运行正常,但是在某些条件下会出问题。这些条件可能永远不会真的形成,或者形成的概率低到在测试过程中根本不会发现。实际上,一般不太可能为并发程序编写生成失败条件的测试用例,这些错误通常又非常偶发,因此最终只会默默转化为客户的抱怨。

这就是学习并发最有说服力的理由之一:一旦忽略,便可能遭到反噬。

并发因此显得充满危险,而如果你因而心生畏惧,那这很大程度上是件好事。虽然Java 8在并发上做了很大的改进,但仍旧未提供诸如编译时校验异常检查那样可以明确告知错误的安全防范手段。在并发层面上,你只能靠你自己。只有抱着求知若渴、敢于质疑和积极向上的态度,你才能编写出可靠的Java并发程序。

09c59e4571b58fa9a06761e886c695c9.png

点击 “阅读原文” 进行试读

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
04-22 46

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值