漫画:高并发下的HashMap

这一期我们来讲解高并发环境下,HashMap可能出现的致命问题。

HashMap的容量是有限的。当经过多次元素插入,使得HashMap达到一定饱和度时,Key映射位置发生冲突的几率会逐渐提高。

这时候,HashMap需要扩展它的长度,也就是进行Resize

影响发生Resize的因素有两个:

 

1.Capacity

HashMap的当前长度。上一期曾经说过,HashMap的长度是2的幂。

2.LoadFactor

HashMap负载因子,默认值为0.75f。

衡量HashMap是否进行Resize的条件如下:

 

HashMap.Size   >=  Capacity * LoadFactor

1.扩容

创建一个新的Entry空数组,长度是原数组的2倍。

2.ReHash

遍历原Entry数组,把所有的Entry重新Hash到新数组。为什么要重新Hash呢?因为长度扩大以后,Hash的规则也随之改变。

让我们回顾一下Hash公式:

index =  HashCode(Key) &  (Length - 1)

当原数组长度为8时,Hash运算是和111B做与运算;新数组长度为16,Hash运算是和1111B做与运算。Hash结果显然不同。

Resize前的HashMap:

Resize后的HashMap:

ReHash的Java代码如下:

 

/** * Transfers all entries from current table to newTable. */void transfer(Entry[] newTable, boolean rehash) {    int newCapacity = newTable.length;    for (Entry<K,V> e : table) {        while(null != e) {            Entry<K,V> next = e.next;            if (rehash) {                e.hash = null == e.key ? 0 : hash(e.key);            }            int i = indexFor(e.hash, newCapacity);            e.next = newTable[i];            newTable[i] = e;            e = next;        }    }}

注意:下面的内容十分烧脑,请小伙伴们坐稳扶好。

假设一个HashMap已经到了Resize的临界点。此时有两个线程A和B,在同一时刻对HashMap进行Put操作:

此时达到Resize条件,两个线程各自进行Rezie的第一步,也就是扩容:

这时候,两个线程都走到了ReHash的步骤。让我们回顾一下ReHash的代码:

假如此时线程B遍历到Entry3对象,刚执行完红框里的这行代码,线程就被挂起。对于线程B来说:

e = Entry3

next = Entry2

这时候线程A畅通无阻地进行着Rehash,当ReHash完成后,结果如下(图中的e和next,代表线程B的两个引用):

直到这一步,看起来没什么毛病。接下来线程B恢复,继续执行属于它自己的ReHash。线程B刚才的状态是:

e = Entry3

next = Entry2

当执行到上面这一行时,显然 i = 3,因为刚才线程A对于Entry3的hash结果也是3。

我们继续执行到这两行,Entry3放入了线程B的数组下标为3的位置,并且e指向了Entry2。此时e和next的指向如下:

e = Entry2

next = Entry2

整体情况如图所示:

接着是新一轮循环,又执行到红框内的代码行:

e = Entry2

next = Entry3

整体情况如图所示:

接下来执行下面的三行,用头插法把Entry2插入到了线程B的数组的头结点:

整体情况如图所示:

第三次循环开始,又执行到红框的代码:

 

e = Entry3

next = Entry3.next = null

 

最后一步,当我们执行下面这一行的时候,见证奇迹的时刻来临了:

newTable[i] = Entry2

e = Entry3

Entry2.next = Entry3

Entry3.next = Entry2

 

链表出现了环形!

 

整体情况如图所示:

此时,问题还没有直接产生。当调用Get查找一个不存在的Key,而这个Key的Hash结果恰好等于3的时候,由于位置3带有环形链表,所以程序将会进入死循环

这种情况,不禁让人联想到一道经典的面试题:

漫画算法:如何判断链表有环?

1.Hashmap在插入元素过多的时候需要进行Resize,Resize的条件是

HashMap.Size   >=  Capacity * LoadFactor。

2.Hashmap的Resize包含扩容和ReHash两个步骤,ReHash在并发的情况下可能会形成链表环。

原文地址:https://www.cnblogs.com/qingyunzong/p/9143249.html

时间: 2024-11-02 08:23:32

漫画:高并发下的HashMap的相关文章

高并发下,HashMap会产生哪些问题?

HashMap在高并发环境下会产生的问题 HashMap其实并不是线程安全的,在高并发的情况下,会产生并发引起的问题: 比如: HashMap死循环,造成CPU100%负载 触发fail-fast 下面逐个分析下出现上述情况的原因: HashMap死循环的原因 HashMap进行存储时,如果size超过(当前最大容量*负载因子)时候会发生resize,首先看一下resize源代码: void resize(int newCapacity) { Entry[] oldTable = table;

高并发下的Java数据结构(List,Set,Map,Queue)

由于并行程序与串行程序的不同特点,适用于串行程序的一些数据结构可能无法直接在并发环境下正常工作,这是因为这些数据结构不是线程安全的.本节将着重介绍一些可以用于多线程环境的数据结构,如并发List.并发Set.并发Map等. 1.并发List Vector 或者 CopyOnWriteArrayList 是两个线程安全的List实现,ArrayList 不是线程安全的.因此,应该尽量避免在多线程环境中使用ArrayList.如果因为某些原因必须使用的,则需要使用Collections.synchr

php结合redis实现高并发下的抢购、秒杀功能

原文: http://blog.csdn.net/nuli888/article/details/51865401 抢购.秒杀是如今很常见的一个应用场景,主要需要解决的问题有两个:1 高并发对数据库产生的压力2 竞争状态下如何解决库存的正确减少("超卖"问题)对于第一个问题,已经很容易想到用缓存来处理抢购,避免直接操作数据库,例如使用Redis.重点在于第二个问题 常规写法: 查询出对应商品的库存,看是否大于0,然后执行生成订单等操作,但是在判断库存是否大于0处,如果在高并发下就会有问

高并发下的 Nginx 优化与负载均衡

高并发下的 Nginx 优化 英文原文:Optimizing Nginx for High Traffic Loads 过去谈过一些关于Nginx的常见问题; 其中有一些是关于如何优化Nginx. 很多Nginx新用户是从Apache迁移过来的,因些他们过去常常调整配置和执行魔术操作来确保服务器高效运行. 有一些坏消息要告诉你, 你不能像Apache一样优化Nginx.它没有魔术配置来减半负载或是让PHP运行速度加快一倍. 高兴的是, Nginx已经优化的非常好了. 当你决定使用Nginx并用a

高并发下的Nginx优化

高并发下的Nginx优化 2014-08-08 13:30 mood Nginx 过去谈过一些关于Nginx的常见问题; 其中有一些是关于如何优化Nginx. 很多Nginx新用户是从Apache迁移过来的,因些他们过去常常调整配置和执行魔术操作来确保服务器高效运行. AD:2014WOT全球软件技术峰会北京站 课程视频发布 11月21日-22日 与WOT技术大会相约深圳 现在抢票 过去谈过一些关于Nginx的常见问题; 其中有一些是关于如何优化Nginx. 很多Nginx新用户是从Apache

php 结合redis实现高并发下的抢购、秒杀功能

抢购.秒杀是如今很常见的一个应用场景,主要需要解决的问题有两个:1 高并发对数据库产生的压力2 竞争状态下如何解决库存的正确减少("超卖"问题)对于第一个问题,已经很容易想到用缓存来处理抢购,避免直接操作数据库,例如使用Redis.重点在于第二个问题 常规写法: 查询出对应商品的库存,看是否大于0,然后执行生成订单等操作,但是在判断库存是否大于0处,如果在高并发下就会有问题,导致库存量出现负数 [php] view plain copy <?php $conn=mysql_con

(高级篇)php结合redis实现高并发下的抢购、秒杀功能

抢购.秒杀是如今很常见的一个应用场景,主要需要解决的问题有两个:1 高并发对数据库产生的压力2 竞争状态下如何解决库存的正确减少("超卖"问题)对于第一个问题,已经很容易想到用缓存来处理抢购,避免直接操作数据库,例如使用Redis.重点在于第二个问题 常规写法: 查询出对应商品的库存,看是否大于0,然后执行生成订单等操作,但是在判断库存是否大于0处,如果在高并发下就会有问题,导致库存量出现负数 优化方案1:将库存字段number字段设为unsigned,当库存为0时,因为字段不能为负数

高并发下linux系统、业务结构性能优化——index(不断更新)

工作中零零散散写了些博客,总结了些知识,当然是从运维的角度.东西一多就乱,闲时突发奇想,这些东西能不能打在一个点上,如果能有一个东西把所有内容串起来并且有一个主题岂不妙哉,也方便查阅和阅读,就像一个网站有了内容后需要一个index主页一样,哈哈,然后就有了这篇置顶博文. 对于主题,我喜欢研究业务架构和大并发相关知识,就定为"高并发下linux系统.业务结构性能优化"了,现有目录结构是根据工作经验进行的梳理,以后会动态修改.我的知识非常有限,不乏有些错误认识,不管怎样抛砖引玉分享出来,希

PHP开发中多种方案实现高并发下的抢购、秒杀功能

抢购.秒杀是如今很常见的一个应用场景,主要需要解决的问题有两个: 1 高并发对数据库产生的压力 2 竞争状态下如何解决库存的正确减少("超卖"问题) 对于第一个问题,已经很容易想到用缓存来处理抢购,避免直接操作数据库,例如使用Redis. 重点在于第二个问题. 常规写法: 查询出对应商品的库存,看是否大于0,然后执行生成订单等操作,但是在判断库存是否大于0处,如果在高并发下就会有问题,导致库存量出现负数 <?php $conn=mysql_connect("localh