jdk1.8的hashmap扩容时会发生数据丢失吗,大家来看看我的说法对不对

📅 2026/7/10 2:48:34 👁️ 阅读次数 📝 编程学习
jdk1.8的hashmap扩容时会发生数据丢失吗,大家来看看我的说法对不对

JDK1.8 HashMap 并发扩容数据丢失问题分析

一、核心结论

JDK 1.8 的 HashMap 仅通过尾插法解决了 JDK1.7 并发扩容时环形链表死循环问题,但没有解决并发扩容导致的数据丢失问题,HashMap 依旧线程不安全,多线程同时触发扩容依然会出现元素丢失、数据覆盖现象。

二、场景前置条件

  1. HashMap 原始数组 table 容量 = 2

  2. 数组下标 1 位置链表:3 -> 7 -> 5

  3. 存在两个并发线程:线程 A、线程 B,同时执行扩容resize()方法

  4. JDK1.8 扩容逻辑:按节点 hash 高位区分,原链表拆分为两条子链表,低位链表留在原下标,高位链表迁移至原下标 + 原容量位置,采用尾插法迁移节点

三、线程调度时序复现数据丢失过程

步骤 1:两线程同时进入扩容逻辑,线程 A 中途阻塞

线程 A、B 同步读取旧数组,开始遍历下标 1 的链表迁移节点;

线程 A 遍历到节点e=3,预存下一个节点引用next=7,此时 CPU 切换,线程 A 被阻塞挂起。

步骤 2:线程 B 完整执行扩容,完成数组替换

线程 B 不受阻塞,完整遍历整条链表3->7->5,按 hash 拆分迁移:

  • 元素 5:hash 高位为 0,留在新数组下标 1;

  • 元素 3、7:hash 高位为 1,迁移至新数组下标 3,形成链表3->7->null

    线程 B 完成全部节点迁移,将扩容后的新数组赋值给 HashMap 全局变量

    table

    ,旧数组引用失效。

此时内存中最新 table 状态:

  • table[1] = 5

  • table[3] = 3 -> 7 -> null

步骤 3:线程 A 恢复时间片,基于旧引用继续遍历丢数据

线程 A 被唤醒后,持有的仍是扩容前旧链表的引用,继续执行循环:

  1. 先处理节点e=3,完成迁移;

  2. 读取预存的next=7,赋值给e,继续处理节点 7;

  3. 读取节点 7 的后继指针7.next = nullnext赋值为 null;

  4. 循环判定next == null,直接终止链表遍历。

步骤 4:最终结果 —— 元素 5 永久丢失

线程 A 只遍历了3、7两个节点,完全没有读取到节点 5,不会对 5 做任何迁移操作;

线程 A 迁移完成后覆盖数组,最终新数组中不存在元素 5,数据丢失