当前位置: 首页 > news >正文

JDK 1.7 HashMap 死循环问题

JDK 1.7 中 HashMap 的链表采用头插法,在多线程同时扩容时可能产生死循环。

扩容代码: 

// JDK 1.7
void transfer(Entry[] newTable) {
    // 原数组
    Entry[] src = table;
    int newCapacity = newTable.length;
    for (int j = 0; j < src.length; j++) {
        // 遍历原数组的桶
        Entry<K,V> e = src[j];
        if (e != null) {
            src[j] = null;
            do {
                // 该节点的后继节点
                Entry<K,V> next = e.next;
                // 原数组的链表在新数组中的位置
                int i = indexFor(e.hash, newCapacity);
                // 头插法,e 的后继节点更新为头节点
                e.next = newTable[i];
                // 头节点更新成 e
                newTable[i] = e;
                // 继续下一个节点
                e = next;
            } while (e != null);
        }
    }
}

死循环发生步骤:

  • 线程 A 和线程 B 同时对同一个链表(A -> B -> C)进行扩容;
  • 线程 A 暂停在 e.next = newTable[i],此时 e 是 A,e.next 是 B;
  • 线程 B 完成扩容,链表变成 C -> B -> A;
  • 线程 A 恢复执行后
      • 将链表头节点置为 A(线程 A 是新的空数组 newTable),A.next = null(newTable[i] == null);
      • 在线程 A 暂停前记录了 next == B,则 e 更新为 B 继续循环;
      • 将链表头节点置为 B,B.next = A(核心问题在这,这个是因为线程 B 导致的,如果没有线程 B,B 的后继节点应该是 C),e 更新为 A 继续循环;
      • 将链表头节点置为 A,此时 A.next = null,将 A.next 置为链表头节点 B,而上一步 B.next = A,产生死循环;
      • 上一步中 next = null,退出循环(可以看出除了死循环外,还会丢失其他节点);

JDK 1.8 后改为尾插法,解决了死循环问题(尾插法中节点顺序是不变的) 。

相关文章:

  • LS-LINUX-002 简易创建SSH
  • 如何实现外观模式?
  • 22 | 如何继续提升 Go 开发技术?
  • java设计模式-外观模式
  • 【人工智能】深入解析自注意力机制:AI大语言模型的核心引擎
  • 球类(继承和多态)
  • configMAX_SYSCALL_INTERRUPT_PRIORITY和configKERNEL_INTERRUPT_PRIORITY
  • 力扣刷题DAY10(动态规划-线性DP)
  • rcore day6
  • [ctfshow web入门] web23
  • cdw2: TypeScript
  • 牛客网:树的高度 ← 根节点为 0 号节点
  • 脚本启动 Java 程序
  • 工程师 - FTDI SPI converter
  • async/await 异步编程
  • 将飞帆制作的网页作为 div 集成到自己的网页中
  • C语言之九九乘法表
  • PCL拟合空间3D圆周 fit3DCircle
  • 数智孪生:制造业转型的驱动力
  • 4月8日日记