HashMap在1.7情况下的多线程死循环问题

本文探讨了Java1.7中HashMap在多线程环境下扩容可能导致的死循环问题,由头插法引发的链表环形结构,以及Java8中通过红黑树改进的解决方案。推荐使用ConcurrentHashMap以避免并发问题。
摘要由CSDN通过智能技术生成

Java 1.7 中 HashMap 的多线程死循环问题主要是由于并发环境下对哈希表进行扩容时,可能会出现环形链表,导致 get() 方法陷入无限循环。这个问题在 Java 8 中得到了解决,因为 Java 8 对 HashMap 的实现做了较大的修改,包括引入红黑树来处理链表过长的问题。

问题产生的原因:

在 Java 1.7 及以前的版本中,HashMap 的 resize() 方法在扩容过程中重新分配元素位置时,是通过头插法来反转链表的。这个过程没有进行同步,所以在多线程并发扩容的情况下,会导致链表出现环形结构。

让我们看一看这个过程具体是如何发生的:

  1. 假设有两个线程 A 和 B 同时对 HashMap 进行写操作,并触发了扩容。

  2. 线程 A 开始执行扩容,它遍历旧的桶数组,取得一个节点,计算它在新数组中的位置,并将其插入到新桶里(头插法)。

  3. 线程 B 同样开始执行扩容,它可能会在线程 A 还没来得及将所有节点移到新桶前,对同一个旧桶里的节点进行操作。

  4. 由于头插法是反转链表,线程 A 和线程 B 在操作相同的节点时可能会导致链表出现环形结构。

问题的源码分析:

在 Java 1.7 的 HashMap 实现中,resize 过程中涉及到的代码如下:

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;
            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;
        }
    }
}

上述代码中 e.next = newTable[i]; 这一行会改变原先节点的 next 指向,如果两个线程同时到达这里,一个线程的更改可能会被另一个线程的更改覆盖,导致链表结构破坏,进而可能形成环形链表。

代码演示:

这个问题是并发编程中的一个经典问题,不容易直接通过代码演示来复现,因为它依赖于线程调度和执行的精确时序。不过,可以用下面的伪代码来模拟这个问题:

// 假设链表只有两个节点e1 -> e2,现在要将这个链表复制到新的表newTable中。

// 线程A 处理 e1
Entry<K,V> e1_next = e1.next; // 记录 e1 的下一个节点 e2
e1.next = newTable[index];    // 将 e1 插入到 newTable 中
newTable[index] = e1;         // 更新 newTable 的头部为 e1
// 线程切换到线程B之前,线程A暂停执行

// 线程B 开始处理 e2
Entry<K,V> e2_next = e2.next; // 记录 e2 的下一个节点,null
e2.next = newTable[index];    // e2.next 指向 e1
newTable[index] = e2;         // newTable 的头部更新为 e2
// 线程切换回线程A

// 线程A 继续执行,使用旧的 e1.next 值
e1.next = newTable[index];    // e1.next 也指向 e2 了,形成环形链表
newTable[index] = e1;         // 实际上这时 newTable[index] 已经是 e2

// 此时 newTable[index] 形成环形链表 e2 -> e1 -> e2 ...

解决办法:

为了避免这种情况,在并发环境下,我们应该使用 ConcurrentHashMap 而不是 HashMapConcurrentHashMap 采用了分段锁(Java 7)和 CAS 操作(Java 8)来减少锁的竞争,从而提供了更好的并发性能,并避免了这种死循环的问题。如果你的应用程序还在使用 Java 1.7 或更早的版本,并且需要处理并发场景,强烈建议升级或改用 ConcurrentHashMap

  • 7
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

辞暮尔尔-烟火年年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值