这个Map你肯定不知道,毕竟存在感确实太低了。 (下)

简介: 这个Map你肯定不知道,毕竟存在感确实太低了。 (下)

畅游源码-REMOVE


接着再看最后一个 remove 方法:

image.png

首先,标号为 ① 的地方,你想到了什么东西?

我看到这个 modCount 可太亲切了。围绕着这个玩意,我前前后后大概写了有 3w 多字的文章吧:

image.png

是为了抛出 ConcurrentModificationException 服务的。

这里体现的是 fast-fail 的思想。

关于这个异常最经典的一个面试题就是:ArrayList 如果一边遍历,一边删除,会出现什么情况?

什么?你不会?我也不回答了。

假粉丝,请你回去等通知吧。

标号为 ② 的地方,把 i 和 i+1 的位置都置为 null。也就是把 key 和对应的 value 都置为 null。

执行完标号为 ② 的地方, remove 的操作也就完成了。

那么按理来说方法就应该结束了。对吗?

image.png

你想一想我之前的这个图片:

image.png

发现问题了吗?

如果这个时候我来查询 key2,而 key2 经过 hash 方法后计算出来的 i 还是 2,而对应位置上的值是 null:

image.png

这个时候你告诉我 key2 查不到,返回一个 null 给我?

key2,啪,没了!


image.png

所以,标号为 ③ 的地方就是为了解决这个问题的。

java.util.IdentityHashMap#closeDeletion

image.png

你看这个方法标号为 ① 的地方,自己都说了:

朋友,因为我们这个结构是一个圆,这个方法比较混乱。做好心理准备。

然后就是一个异常复杂的 if 判断。

这个我是看懂了,但是属于只可意会不可言传的那种,所以就不给大家分析了。大家有兴趣的自己去看看。

只要你抓准了它的存储机制和方法功能,理解起来应该不算很费劲。

image.png

再看标号为 ② 的地方,理解起来就很容易了,把之前由于 hash 冲突导致的位置偏移的数据,一个个的往前挪:

image.png

意思就是上面图片的意思。

先把 key1 从 i=2 的位置移走。然后把 i=4 的 key2 往前移动 2 位。

这样,下次来查询 key2 的时候,就能得到正确的返回了。

这里留下一个疑问,假设下面这个场景:

image.png

key1 和 key2 是有 hash 冲突的,但是 key3 是正常的。

那么移除掉 key1 之后的图应该是这样的:

image.png

代码是怎么控制或者说怎么知道 key2 和 key1 是有冲突的,所以移走 key1 之后,需要把 key2 往前移动。而 key3 和 key2 是没有关系的,所以 key3 放着不动。

答案其实就藏在 closeDeletion 方法的源码里面,就看你有没有彻底理解这个方法了。

好了,到这里关于 identityHashMap 增删改查我们就分享完毕了。

老规矩,源码导读,点到为止。

就像传统功夫,都是点到为止。年轻人,不讲武德,耗子尾汁...

image.png

马老师可真是我最近一段时间的快乐源泉啊。

咦,偏了偏了,说编程呢,怎么说到马老师那边去了。

难道我不经意间发现了:万物皆可马保国定律?


identityHashCode的错误使用


前面说了,IdentityHashMap 的核心点在于 System.identityHashCode 方法。

说到这个 identityHashCode 我又想到了曾经在 Dubbo 中的看到的一段源码。

位于一致性哈希负载均衡算法中:

org.apache.dubbo.rpc.cluster.loadbalance.ConsistentHashLoadBalance#doSelect

image.png

上面的源码是 2.7.8 版本。

假设有五个可用的服务提供者,这里的 invokers 集合里面装的就是一个个服务提供者。

然后调用了 invokers ,也就是 list 的 hashCode 方法。

因为一致性哈希的负载均衡的思想就是当服务发生了上下线之后,我们需要对哈希环进行调整。

如果服务没有发生上下线,那么是不需要进行哈希环调整的。

具体到这个 list 来说就是:

当 list 里面的元素发生了变化,那么说明有服务上下线的情况发生。

至于你装元素的 list 是否和原来的不一样,那我是不关心的。

所以作者在这里还写了一个备注:我们应该只注意 list 里面的元素就可以了。

言外之意就是我刚刚说的:装元素的 list 是否发生了变化,我是不关心的。

按照开源框架的尿性,这地方专门写了一行注释,说明这个地方曾经是有问题的。

那我们看看这个地方的提交记录:

image.png

image.png

你看,原来的代码是 System.identityHashCode 方法。

后来修改为调用 list 的 hashCode 方法。

单单看着一行代码,我们就知道,之前的代码是关注 list 这个容器了,导致了某些 bug 的出现。

具体什么原因,我们可以看看这次提交对应的 pr:

也就是编号为 5429 的 issue:

https://github.com/apache/dubbo/issues/5429

image.png

哎呀,我去,这谁啊?看着眼熟啊?这不就是 why 哥吗?这不是巧了吗,这不是?

是的,这个 bug 就是我发现并提出的对应的 issue。

image.png

而且这个 bug 其实是非常好发现的,只要你把环境一搭,代码一跑,场景一模拟。是个必现的问题。

而产生这个 bug 的原因,可谓是蝴蝶效应。在离这段源码很远的,毫不相干的一次需求中,不知不觉的就影响到了这段代码。

而且连开发者自己都不知道,自己的修改会影响到一致性哈希负载均衡算法。所以,根本也就谈不上什么测试用例了。

如果你想更进一步了解这个 bug 的来龙去脉。可以看看这篇文章:

《够强!一行代码就修复了我提的Dubbo的Bug》

如果你想更进一步的了解 Dubbo 的负载均衡策略,那可以看看这篇文章:

《吐血输出:2万字长文带你细细盘点五种负载均衡策略。》

好了,那么这次的文章就到这里啦。给大家分享了一个冷门的、"学了没多大卵用" 的 IdentityHashMap。

你要是不喜欢下面的荒腔走板环节的话,也请记得拉到文章的最后。留言、点赞、在看、转发、赞赏,随便来一个就行。你要是都安排上,我也不介意。


荒腔走板


最近项目组接到了一个工期特别紧张的项目。

所以刚刚过去的周末我加了两天的班。周六晚上把流程走通之后,已经快是 22 点了。

之前预约了安装家电的师傅,刚好也是周六。

所以只有女朋友一个人去家那边,边打扫卫生,边等着安装师傅。

安装师傅全部弄好之后也是 19 点之后了。

因为我从公司到家特别的近。女朋友觉得我也差不多该下班了,于是决定就在家里等我,然后一起从家里回到租住的小区。

结果一等就是 2 个多小时。

我下班之后,马上打车到小区。

下午没有吃饭,工作也比较劳累,坐在车上,一阵疲倦的感觉袭来。

但是在小区门口刷门禁卡的时候,我一抬头,门口写着:欢迎回家。

那一刻,我突然觉得好暖啊,甚至还有一丝丝的感动。

走在小区的路上,感觉一切都是这么的可爱。

因为这个家,真的是属于自己的家,用自己一手一脚挣出来的钱堆出来的。

此时此刻,家里还有一个人,开着灯,在等着我回家。

之前我从来没有这样的感觉过,这是一种非常神奇的感觉。

到家之后,由于家具还没有准备好,我看到女朋友在地上铺着一个泡沫垫子,坐在上面,靠在墙上,通过手机看着综艺。

她起来抱了抱我,说:你终于回来啦。今天的事可真是多。

我们一起站在空荡荡的客厅中间。

那一刻,家的含义,家的感觉,从来没有这么具体过。


最后说一句(求关注)


才疏学浅,难免会有纰漏,如果你发现了错误的地方,可以在留言区提出来,我对其加以修改。 感谢您的阅读,我坚持原创,十分欢迎并感谢您的关注。

我是 why,一个被代码耽误的文学创作者,不是大佬,但是喜欢分享,是一个又暖又有料的四川好男人。

欢迎关注我呀。


相关实践学习
部署高可用架构
本场景主要介绍如何使用云服务器ECS、负载均衡SLB、云数据库RDS和数据传输服务产品来部署多可用区高可用架构。
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
目录
相关文章
|
Oracle Java 关系型数据库
使用了这个神器,让我的代码bug少了一半(下)
使用了这个神器,让我的代码bug少了一半(下)
使用了这个神器,让我的代码bug少了一半(下)
|
10月前
不是工作不好找,是你真的不行
不是工作不好找,是你真的不行
|
8月前
|
测试技术
代码为啥不能过度优化
代码为啥不能过度优化
42 0
|
安全 Windows
这5款软件虽然知名度不高,但不代表不好用
其实有许多工具,知名度不高,用的人也很少,不过并不代表它们不好用,小编励志做一个合格的搬运工,让大家都能用上好用的软件。
76 1
BeyondCompare4无限使用办法
BeyondCompare4无限使用办法
147 0
BeyondCompare4无限使用办法
|
算法 搜索推荐 程序员
再也不担心用不好二分法了,因为我找到了"作弊"的接口
导读:算法是程序的灵魂,而复杂度则是算法的核心指标之一。为了降低复杂度量级,可谓是令无数程序员绞尽脑汁、甚至是摧枯秀发。一般而言,若能实现对数阶的时间复杂度,算法效率往往就已经非常理想。而实现对数阶的常用思想莫过于二分。 二分常有,好用的二分并不常有。while条件是lo<hi还是lo<=hi?分支判断mid是+1还是-1还是仍然取值mid?最后return哪个值?如果目标序列不是严格递增又该怎么处理?想想都不禁让人敬而远之。幸运的是,在python语言中,已经内置了成熟的二分函数。
116 0
再也不担心用不好二分法了,因为我找到了"作弊"的接口
|
安全 Java 测试技术
使用了这个神器,让我的代码bug少了一半(上)
使用了这个神器,让我的代码bug少了一半
使用了这个神器,让我的代码bug少了一半(上)
|
存储 API
这个Map你肯定不知道,毕竟存在感确实太低了。 (中)
这个Map你肯定不知道,毕竟存在感确实太低了。 (中)
111 0
这个Map你肯定不知道,毕竟存在感确实太低了。 (中)
|
存储 Dubbo Java
这个Map你肯定不知道,毕竟存在感确实太低了。 (上)
这个Map你肯定不知道,毕竟存在感确实太低了。 (上)
77 0
这个Map你肯定不知道,毕竟存在感确实太低了。 (上)
|
存储 缓存 安全
ConcurrentHashMap 有十个提升性能的地方,你都知道吗?
如何在高并发下提高系统吞吐是所有后端开发者追求的目标,Java并发的开创者Doug Lea在Java 7 ConcurrentHashMap的设计中给出
ConcurrentHashMap 有十个提升性能的地方,你都知道吗?