【JDK1.6】HashMap死循环形成原因

目录

1.扩容形成环路

2.get(key)操作死循环


1.扩容形成环路

在put元素超过负载阈值时会触发HashMap的扩容resize操作,一个桶的链表会重新散列到新表中,

    /** 
    * put 插入元素之后,负载超过阈值,触发resize方法扩容 
    */  
   void addEntry(int hash, K key, V value, int bucketIndex) {  
       Entry<K,V> e = table[bucketIndex];  
       table[bucketIndex] = new Entry<K,V>(hash, key, value, e);  
       if (size++ >= threshold)  
           resize(2 * table.length);  
   }  
  
   /** 
    * resize(),transfer把旧表中的元素添加到新表中
    */  
   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);
       table = newTable;  
       threshold = (int)(newCapacity * loadFactor);  
   }  

    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; //最严重的部分,先保留了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;        // 链向原表头
            } // while
        }
    }
操作 解释
put

map.put(3,"A");

map.put(7,"B");插入链头 如图

有两个桶的table,

threshold=cap*DEFAULT_LOAD_FACTOR=1

第二次put就会触发扩容
if (++size > threshold)
    resize();
resize

并发扩容,

如果线程B在拷贝新表第一行已经拿到next节点后发生切换,

线程A正常拷贝结束,插入队头

线程B切回继续拷贝,就会形成环路

且由于可见性问题,扩容操作的遍历可以结束,因为next的next在此时的高速缓存里是NULL。

get 获取15时,会落在tab[2]里,会遍历链表,链表中没有命中,就会一直遍历下去(回头路)!

2.get(key)操作死循环

HashMap在get操作一直在抢占CPU,按道理get操作是平均O(1)的,不太会造成这种现象

那是因为get操作需要遍历拉链后的链表,链表如果key不命中,就一直循环下去。

发布了121 篇原创文章 · 获赞 111 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/sarafina527/article/details/105140395