美文网首页程序员
HashMap死循环问题

HashMap死循环问题

作者: _心碎乌托邦_ | 来源:发表于2020-05-05 16:20 被阅读0次

    一、HashMap简介:

    1、数据结构

    image.png

    数组+链表的结构,每一对k:v都封装成一个entry,通过链表解决冲突和碰撞。

    put(k, v):首先对key哈希找到table对应的位置index,如果该位置没有entry,直接放进去,如果该位置已经有entry了,插入表头;

    当table里的元素达到一定阈值的时候,通过rehash扩大table的大小(2倍)降低冲突/碰撞率;

    2、HashMap存在的问题:

    • 线程不安全

    • 丢失数据:put进去一个元素,get却是null

    • 死循环,CPU利用率被打满

    二、一个完美的HashMap死循环是如何产生的

    如何产生的:多线程,put操作导致rehash,把元素从旧table拷贝(transfer)到新table中时,可能产生死循环。

    void transfer(Entry[] newTable, boolean rehash) {
      int newCapacity = newTable.length;
      //下面这段代码的意思是:从OldTable里摘一个元素出来,然后放到NewTable中
      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;
            }
        }
    }
    

    1、正常的ReHash过程

    我假设了我们的hash算法就是简单的用key mod 一下表的大小(也就是数组的长度)。

    最上面的是old hash 表,其中的Hash表的size=2, 所以key = 3, 7, 5,在mod 2以后都冲突在table1这里了。

    接下来的三个步骤是Hash表 resize成4,然后所有的 重新rehash的过程。


    image.png

    2、并发的rehash过程

    do {
        Entry<K,V> next = e.next; // <--假设线程一执行到这里就被调度挂起了
        int i = indexFor(e.hash, newCapacity);
        e.next = newTable[i];
        newTable[i] = e;
        e = next;
    } while (e != null);
    

    假设线程1在上述代码注释处被挂起了,而我们的线程二执行完成了,于是我们有下面的这个样子:


    image.png

    此时线程1回来继续执行:


    image.png
    线程一接着工作,把key(7)摘下来,放到newTable[i]的第一个,然后把e和next往下移:
    image.png

    环形链出现:


    image.png
    此时如果我们执行map.get(11)就会陷入死循环,慢慢耗尽CPU。

    三、解决方案

    1. 用HashTable替换HashMap
    2. 加锁,线程同步
    3. 用concurrentHashMap替换HashMap

    参考:https://www.cnblogs.com/andy-zhou/p/5402984.html

    相关文章

      网友评论

        本文标题:HashMap死循环问题

        本文链接:https://www.haomeiwen.com/subject/segmghtx.html