HashMap中的无限循环

此博客将集中解决以下问题:
为什么HashMap不应该在多线程环境中使用?
他能导致无限循环吗?
当get方法在HashMap中进入无限循环时?


如果在多线程环境中使用HashMap,则Get和Put操作可能引导你进入无限循环


hashmap-get-operation-goes-to-infinite-loop.png

什么是rehashing?

HashMap的默认容量为16,加载因子为0.75,这意味着当第13个 键值对在地图中输入时,HashMap将使其容量加倍(16 * 0.75 = 12)。

直到第12个键值对,Hashmap将继续将项目放在地图中,一旦您尝试放置第13个键值对,重新开始进程就会重新开始。

加载因子: 加载因子是一种度量“直到什么加载,hashmap可以允许元素在其大小增加之前放入其中。”
未命名文件.png

Rehasing反转节点的排序

在Rehashing过程中,
Hashmap首先创建一个双倍大小的新数组(Buckets)。
Hashmap 将旧存储桶中的键值对传输到新存储桶。
键值对将在新存储桶中反转,因为Hashmap将在新存储桶的开头添加键值对,而不是在结尾处添加键值对。
Hashmap在开始时添加新的键值对,以避免每次遍历链表并保持不变的性能。


让我们看看转移过程,例如,
HashMap的无限循环,工作.png

当2个线程尝试将第13个键值对放入Hashmap时会发生什么?

当2个线程同时尝试访问HashMap时,您可能会遇到无限循环。
让我们看看它是如何发生的,

为了清楚起见,让我们将2个线程命名为线程1和线程2,并且两者都尝试放置第13个键值对。

很明显,在放入第13个键值对之前,Hashmap必须首先执行Rehashing过程,因为第13个键值对超过了加载因子限制。
此外,这里的Hashmap由Thread1和Thread2访问,因此无法保证哪个Thread将首先获得访问权限。

我们假设两个线程都到达一个地方,在那里它们都识别出载荷因子限制已经越过并且地图需要重新划分。这是两个线程将尝试调用以下方法来将旧桶中的键值对传输到新桶的地方。

调用方法transfer()以将键值对从

旧桶传输到新桶
void transfer(Entry[] newTable) {<font></font>
    Entry[] src = table;<font></font>
    int newCapacity = newTable.length;<font></font>
    for (int j = 0; j &lt; src.length; j++) {<font></font>
        Entry<k> e = src[j];<font></font>
        if (e != null) {<font></font>
            src[j] = null;<font></font>
    <font></font>
            // --------- 下面的代码会产生问题  --------- <font></font>
            do {<font></font>
    <font></font>
                Entry<k> next = e.next; <font></font>
                int i = indexFor(e.hash, newCapacity);<font></font>
                e.next = newTable[i];<font></font>
                newTable[i] = e;<font></font>
                e = next;<font></font>
<font></font>
            } while (e != null);<font></font>
            // --------- 直到这里 --------- <font></font>
<font></font>
        }<font></font>
    }<font></font>
}<font></font>

让我们看看Hashmap如何以无限循环结束。
下面你将看到线程1和线程2步骤,以便快速浏览。
注意:如果您在理解Thread步骤时遇到困难,可以转到后面的部分,其中详细解释了transfer()方法和Thread Steps。


线程1有机会执行。

执行第一行内部循环(在transfer()方法中)后,线程1指向第一个键值对和下一个(第二个)键值对以开始传输过程。

在执行任何步骤之前,线程1松开控件,线程2有机会执行。

因此,线程1的当前状态是, e =节点90,下一个=节点1。


HashMap的无限循环线程1步 - 总结 - 前.png

线程2有机会执行。


  • 1.幸运的是,线程2执行完整的transfer()方法而不会失去对其他线程的控制。

  • 2.在将旧桶中的键值对传输到新存储桶时,键值对将在新存储桶中反转,因为hashmap将在开始时而不是在结尾处添加键值对。这样做是为了避免每次遍历链表并保持不变的性能。

  • 3.线程2将所有键值对从旧存储桶传输到新存储桶,线程1将有机会执行。
    线程2执行后,Hashmap状态如下图所示,


    散列映射无限循环线程-2-步骤-摘要.png

线程1再次有机会执行。


现在线程1将恢复transfer()过程,但它将以Infinte循环结束节点。
这发生因为线程2实际上反转了节点链接。


散列映射无限 - 环 - 线程1用步骤-汇总后.png

任何进一步的get / put请求都将以无限循环结束。

还不清楚它是如何在无限循环中结束的?
让我们逐步了解详细的逐步算法中两个线程的每个步骤。

在进入细节之前让我们了解transfer()方法的作用:


第1 步: 输入<k> next = e.next;
无限循环功能于散列映射-重散列码解释的步1.png
第2 步: int i = indexFor(e.hash,newCapacity);
无限循环功能于散列映射-重散列码解释的步2.png
第3 步: e.next = newTable [i];
无限循环功能于散列映射-重散列码解释的步3.png
第4 步: newTable [i] = e;
无限循环功能于散列映射-重散列码解释的步4.png
注意:
无限循环功能于散列映射-重散列码解释的步-4-部分-2.png
第5步: e =下一个;
无限循环功能于散列映射-重散列码解释的步5.png

太多的代码....现在让我们从竞争条件开始....


线程1步骤

线程1有机会执行,并执行以下步骤,
  • 1.线程1尝试放入第13个键值对,

  • 2.线程1发现达到阈值限制并且它创建了容量增加的新桶。因此地图的容量从16增加到32。

  • 3.线程1现在开始传输过程,用于将存储在桶0
    的存储键值从旧数组传输到存储桶0处的新数组(假设为在新数组中存储键值对而评估的索引与索引0相同)。

对于传输,它调用transfer()方法并进入循环。

  • 4.执行第一行内部循环(在transfer()方法中)后,线程1指向第一个键值对和下一个(第二个)键值对以开始传输过程。

在执行任何步骤之前,线程1松开控件,线程2有机会执行。

  • 5.因此,线程1的当前状态是, e =节点90,下一个=节点1。


    无限循环线程1 state.png

线程1指向键值对之后,在开始传输过程之前,松开控件,线程2有机会执行。


线程2步骤

线程2有机会执行,并执行以下步骤,
  • 1.线程2尝试放入第13个键值对,

  • 2.线程2发现达到阈值限制并且它创建了容量增加的新桶。因此地图的容量从16增加到32。

  • 3.线程2现在开始传输过程,用于将存在于存储桶0
    的存储键值从旧数组传送到存储桶0处的新数组(假设为在新数组中存储键值对而评估的索引与索引0相同)。

    对于传输,它调用transfer()方法并进入循环。

  • 4.线程2指向第一个键值对和下一个(第二个)键值对以开始传输过程。

  • 5.幸运的是,线程2执行完整的transfer()方法而不会失去对其他线程的控制。

  • 6.在将旧桶中的键值对传输到新存储桶时,键值对将在新存储桶中反转,因为hashmap将在开始时而不是在结尾处添加键值对。这样做是为了避免每次遍历链表并保持不变的性能。

  • 7.线程2将所有键值对从旧存储桶传输到新存储桶,线程1将有机会执行。

线程2执行后,Hashmap状态如下图所示

无限循环线程-2- state.png

这里就没有吧线程1和线程2的详细执行步骤的图罗列出来。可根据自我理解情况查看源代码


如有错误,欢迎留言联系作者

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,717评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,501评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,311评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,417评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,500评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,538评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,557评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,310评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,759评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,065评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,233评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,909评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,548评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,172评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,420评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,103评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,098评论 2 352

推荐阅读更多精彩内容