`
cnjarchen
  • 浏览: 43507 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

HashMap的resize死循环

 
阅读更多

       当HashMap有数据插入时,都会检查容量有没有超过设定的thredhold,如果超过就要做rehash操作。代码如下:

void resize(int newCapacity) {
Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        Entry[] newTable = new Entry[newCapacity];
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
 
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;
            }
        }
}

        大概看下transfer的步骤:

  1.         1.对索引数组中的元素遍历
  2.         2.对链表上的每一个节点遍历:用 next 取得要转移那个元素的下一个,将 e 转移到新 Hash 表的头部,使用头插法插入节点。
  3.         3.循环2,直到链表节点全部转移
  4.         4.循环1,直到所有索引数组全部转移

        经过这几步,我们会发现转移的时候是逆序的。假如转移前链表顺序是1->2->3,那么转移后就会变成3->2->1。这时候在多线程场景下,就会产生死锁问题了,因为可能会造成1->2的同时2->1。

 

         关键代码分析:

while(null != e) {
     Entry<K,V> next = e.next;
     e.next = newTable[i];
     newTable[i] = e;
     e = next;
}

    1. Entry<K,V> next = e.next;——因为是单链表,如果要转移头指针,一定要保存下一个结点,不然转移后链表就丢了

 

    2. e.next = newTable[i];——e 要插入到链表的头部,所以要先用 e.next 指向新的 Hash 表第一个元素(为什么不加到新链表最后?因为复杂度是 O(N))

 

    3. newTable[i] = e;——现在新 Hash 表的头指针仍然指向 e 没转移前的第一个元素,所以需要将新 Hash 表的头指针指向 e

 

    4. e = next——转移 e 的下一个结点      

 

    假设HashMap某位的链表有两个对象,且有两个线程同时执行了put()操作,并进入了transfer()环节。 线程1在执行到第1步时挂起了,然后线程2先执行完毕。线程1继续执行。执行到转移到链表中第二个对象时,由于线程2已执行完,把对象2本应是null的next变成了对象1,于是transfer又继续执行转移对象1,于是对象1的next指向对象2,环形链表出现了

 

    详见http://www.importnew.com/22011.html

分享到:
评论

相关推荐

    疫苗:Java HashMap的死循环

    Java HashMap的死循环原因分析 HashMap是Java中一种常用的数据结构...HashMap的死循环问题是由resize操作引发的,而resize操作是HashMap的内部机制。为了避免死循环问题,我们应该使用ConcurrentHashMap代替HashMap。

    HashMap put方法的源码分析

    从Java 1.7到1.8,HashMap经历了重大改进,尤其是在解决死循环问题上。本文将深入解析Java 1.8中HashMap的put方法源码,探讨其内部工作原理。 首先,我们了解HashMap的基本结构。在Java 1.8中,HashMap由数组加链表...

    关于如何解决HashMap线程安全问题的介绍

    在多线程环境下,两个线程同时触发扩容可能导致循环链表的形成,从而引发死循环,这是一种严重的性能问题。 为了解决HashMap的线程不安全问题,我们可以采取以下几种策略: 1. 使用Collections.synchronizedMap()...

    jdk1.7 HashMap中的致命错误:循环链表

    然而,在JDK1.7版本中,HashMap存在一个严重的问题,即“循环链表”(Looping List),这可能导致在多线程环境下性能急剧下降,甚至引发死循环。本文将深入探讨这个问题及其解决方案。 首先,我们来看看JDK1.7 ...

    高级程序员必会的HashMap的线程安全问题,适用于0~2年的.7z

    3. **死循环(死锁)**:在极端情况下,由于HashMap的迭代器依赖于table的状态,如果在迭代过程中table结构发生变化(比如resize),可能会造成迭代器陷入死循环。 为了解决这些问题,有以下几种策略: 1. **使用...

    java7hashmap源码-backend-study:后端学习之路

    java7 hashmap源码 随着Java学习的不断深入,发现...多线程下,hashmap的resize()方法为什么容易出现死循环? 答: 其他面试题? 答: 并发 概述 :star::star: :star::star: 线程池 :star: AQS :star: 锁 ListenalbeFut

    面试题之详解HashMap

    本文将详细解析HashMap的一些常见面试题,包括HashMap的长度为什么是2的幂次方、多线程操作下的死循环问题、底层实现以及扩容机制。 1. HashMap长度为什么是2的幂次方? 这个设计主要是为了优化哈希函数的效率。在...

    常见的面试点1

    5. 并发问题:HashMap在并发环境下可能导致死循环,因为1.7版本使用尾插法。1.8版本虽然使用头插法,但在并发情况下仍然不安全。面试中,你可能会被问到如何在并发环境中安全地使用HashMap,这时可以提到`HashTable`...

    JAVA+面试题+常用的比较齐全

    8. HashMap线程不安全体现在并发操作时可能出现数据丢失或死循环。JDK1.7的头部插入可能导致死循环,而JDK1.8改为了尾部插入以解决此问题。 9. 加载因子设置为0.75是考虑到容量与性能的平衡,太小可能导致频繁扩容...

    sesvc.exe 阿萨德

    但是 HashMap 原有的问题也都存在,比如在并发场景下使用时容易出现死循环。 final HashMap, String&gt; map = new HashMap, String&gt;(); for (int i = 0; i ; i++) { new Thread(new Runnable() { @Override public...

    阿里巴巴/招行信用卡中心21届实习面试知识点汇总

    - **链表插入方式**:JDK 1.8前的头插法可能导致死循环,1.8后的尾插法避免了死循环,但可能导致值丢失。 - **解决方案**:使用线程安全的ConcurrentHashMap,其采用分段锁机制,提高并发性能。 4. **红黑树**: ...

Global site tag (gtag.js) - Google Analytics