hashMap 在多线程中会出现什么问题

**

HashMap 每次扩容的时候resize 都会重新计算下hash,找到扩容后的位置也就是要rehash

**

1.丢失元素

1.当多线程同时put值的时候,若发生hash碰撞,可能多个元素都落在链表的头部,从而造成元素覆盖(hashcode相同而eques值不同的元素)

列如:线程A put一个元素a ,线程B put一个元素b,a,b 发生hansh碰撞,本应该在map是链表的形式存在,但是可能线程A和线程B同时put到链表的第一个位置,从而后来者覆盖前者元素造成元素丢失。

2.put 造成链表形成闭环,get的时候出现死循环(jdk8已经解决该问题)

该情况是出现在多线线程操作map扩容时会发生

jdk1.7的resize源码一探究竟

// 扩容方法
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);
}

/**
 * Transfers all entries from current table to newTable.
 * 这段代码主要是遍历原集合中的所有Entry, 然后依次将他们放入到新的集合中.
 */
void transfer(Entry[] newTable, boolean rehash) {
     
    int newCapacity = newTable.length;
    
    // 遍历所有Entry
    for (Entry<K,V> e : table) {
     
    
        // 这里的while主要针对存在链表的情况
        while(null != e) {
     
        
            // 获取下一个元素, 如果存在链表, next就不为null
            Entry<K,V> next = e.next;
            
            // 是否需要重新hash
            if (rehash) {
     
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            
            // 获取新下标
            int i = indexFor(e.hash, newCapacity);
            
            // 第一次while循环时, newTable[i]是null
            // 第二次while循环时, newTable[i]是第一次循环时的元素
            // 原先链表的顺序为: 1,3,5,7,9
            // 正常情况下, 扩容完成之后, 链表中元素的顺序为: 9,7,5,3,1
            e.next = newTable[i];
            
            // 覆盖上次循环的值, 因为上次循环时的值已经被链接到e.next上了
            newTable[i] = e;
            
            // 继续循环链表上的下一个元素
            e = next;
        }
    }
}

形成循环链表的代码就在transfer方法的while循环中, 正是因为扩容之后链表中元素的会发生逆转, 所以会产生循环链表.
举例说明
/**

  • [ 1, 3, 5, 7, 9] // HashMap table
  • 11
  • 12
  • 13
  • 14
  • 15
    */
    可以发现, 集合中下标0处发生了hash冲突, 产生了链表: 1,11,12,13,14,15.

现有两个线程: 线程A和线程B, 线程A进入while循环时, 执行到Entry next = e.next;时被挂起了, 这时该线程A中的e=1, e.next=11. 此时线程B进入while循环, 这时线程B中的e=1, e.next=11, 然后线程B继续向下执行, 执行完第一次while循环之后, 链表的顺序就变为11,1,12,13,14,15. 这时元素11的下一个元素时1,
而此时线程A中元素1的下一个元素为11. 完美! 产生了循环链表!

通俗来讲:线程A和线程B 操作了通一个链表e ,造成了这种结果。
jdk1.8中改进了resize方法,改进之后的方法不再进行链表的逆转, 而是保持原有链表的顺序, 如果在多线程环境下, 顶多会在链表后边多追加几个元素而已, 不会出现环的情况.

你可能感兴趣的:(java)