JDK1.7 HashMap并发安全问题

本文涉及的产品
函数计算FC,每月15万CU 3个月
性能测试 PTS,5000VUM额度
应用实时监控服务-应用监控,每月50GB免费额度
简介: 虽然我们现在开发中使用的JDK一般都是JDK8了但是最近还是有很多同学在面试的过程中遇到了JDK1.7的一些问题那么接下来我们就带着大家一起来聊一下HashMap在1.7中的各种问题。

虽然我们现在开发中使用的JDK一般都是JDK8了但是最近还是有很多同学在面试的过程中遇到了JDK1.7的一些问题那么接下来我们就带着大家一起来聊一下HashMap在1.7中的各种问题。

​ 出现问题主要是由于扩容的如下代码导致的:

void transfer(Entry[] newTable) {
   
   
    Entry[] src = table;
    int newCapacity = newTable.length;
    for (int j = 0; j < src.length; j++) {
   
   
        Entry e = src[j];
        if (e != null) {
   
   
            src[j] = null;
            do {
   
   
                Entry next = e.next;
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            } while (e != null);
        }
    }
}

一、put一个数据get返回null

​ 从上面代码中可以看到扩容的时候会把存储数据的table数组赋值给src数组然后遍历src里面的每一个元素看是否为空的如果为空则把src[j]置为空,此时就出问题了。

原本应该获取到open的但是现在可能只能获取到null了,主要出现的原因是线程一扩容的时候会对table中的数据进行修改,此时线程二来获取可能就获取到的是修改后的值(null),当线程一操作完成以后线程二再次获取也不会出现该问题,该问题只会出现在扩容中。

二、扩容的实现出现循环链表

​ 还是上面那一段代码,如果是在单线程的环境下执行扩容是不会有任何问题的,大致的扩容如下:

​ 从扩容的过程中大家可以看到扩容的时候是把原来的链表顺序倒置然后插入到新的下标中,这个过程其实就是经常说的头插法也就是这个问题导致了循环链表的问题,那么接下来就一起来看下多线程环境下是如何形成的循环链表的:

​ 线程一:

do {
   
   
    Entry next = e.next; //线程一执行到这一行的时候挂起
    int i = indexFor(e.hash, newCapacity);
    e.next = newTable[i];
    newTable[i] = e;
    e = next;
} while (e != null);

​ 线程二:完整的执行完成所有的扩容操作


​ 从图中可以看到当线程一进行第三次循环的时候就已经形成了循环链表了。

三、丢失数据

​ 上图除了描述扩容的过程中会形成循环链表以外还描述了一个问题就是在扩容的时候有一个数据丢失了,原因是因为next指向的是k2现在k2的next指向的不是k3而是k1所以k3的数据不能拷贝到新的数组中所以出现数据丢失。

四、Cpu占用100%

​ 在多线程环境下我们通过get方式去获取数据的时候,可能出现cpu占用100%的情况,主要的原因是我们hash计算索引的时候计算到了2,然后2这个下标里面又有一个循环链表,只要我们进入该循环链表我们的程序就出不来了,然后cpu就会占用到100,同时也获取不到数据,这个其实也是一个隐形的数据丢失的情况,虽然数据存在但是因为进入了循环链表所以数据怎么都获取不到。

五、如何解决以上的问题

  1. 使用线程安全的HashTable替换HashMap,当然现在HashTble已经几乎在项目中看不到了
  2. 通过Collections.synchronizedMap获取到一个线程安全的HashMap
  3. ConcurrentHashMap替换HashMap【下一篇文章我们就将讲解ConcurrentHashMap】

六、JDK1.8如何解决循环链表的问题

​ JDK1.8采用的是数据+链表+红黑树的方式来实现的,同时他重构了resize的迁移方法,把HashMap的头插法修改为了所谓的尾插法,他保证了数据迁移的顺序问题。

do {
   
   
    next = e.next;
    // 表示老值链表,即该链表中计算出索引位置不变的元素
    if ((e.hash & oldCap) == 0) {
   
   //核心代码
        if (loTail == null)
            loHead = e;
        else
            loTail.next = e;
        loTail = e;
    }
    // 表示新值链表,即计算出索引位置发生变化的元素
    else {
   
   
        if (hiTail == null)
            hiHead = e;
        else
            hiTail.next = e;
        hiTail = e;
    }
    } while ((e = next) != null);
    // 生成链表后整体赋值
    // 老链表整体赋值
    if (loTail != null) {
   
   
        loTail.next = null;
        newTab[j] = loHead;
    }
    // 新链表整体赋值
    if (hiTail != null) {
   
   
        hiTail.next = null;
        newTab[j + oldCap] = hiHead; //核心代码
    }
}

​ 因为JDK1.8的resize中会牵涉到红黑树的实现,很多的同学如果没有学过数据结果可能听不懂所以再次我就不打算再去展开讲解了,以后有机会给大家分享数据结构的内容,听懂了以后再回过头来查阅这段代码。

目录
相关文章
|
6月前
|
Java 调度 开发者
JDK 21中的虚拟线程:轻量级并发的新篇章
本文深入探讨了JDK 21中引入的虚拟线程(Virtual Threads)概念,分析了其背后的设计哲学,以及与传统线程模型的区别。文章还将讨论虚拟线程如何简化并发编程,提高资源利用率,并展示了一些使用虚拟线程进行开发的示例。
1081 4
|
6月前
|
存储 缓存 并行计算
【面试问题】JDK并发类库提供的线程池实现有哪些?
【1月更文挑战第27天】【面试问题】JDK并发类库提供的线程池实现有哪些?
|
6月前
|
Java
【JDK 源码分析】HashMap 操作方法
【1月更文挑战第27天】【JDK 源码分析】HashMap Put 元素 初始化
|
1月前
|
Java
Java基础之 JDK8 HashMap 源码分析(中间写出与JDK7的区别)
这篇文章详细分析了Java中HashMap的源码,包括JDK8与JDK7的区别、构造函数、put和get方法的实现,以及位运算法的应用,并讨论了JDK8中的优化,如链表转红黑树的阈值和扩容机制。
26 1
|
2月前
|
安全 Java API
【性能与安全的双重飞跃】JDK 22外部函数与内存API:JNI的继任者,引领Java新潮流!
【9月更文挑战第7天】JDK 22外部函数与内存API的发布,标志着Java在性能与安全性方面实现了双重飞跃。作为JNI的继任者,这一新特性不仅简化了Java与本地代码的交互过程,还提升了程序的性能和安全性。我们有理由相信,在外部函数与内存API的引领下,Java将开启一个全新的编程时代,为开发者们带来更加高效、更加安全的编程体验。让我们共同期待Java在未来的辉煌成就!
66 11
|
2月前
|
安全 Java API
【本地与Java无缝对接】JDK 22外部函数和内存API:JNI终结者,性能与安全双提升!
【9月更文挑战第6天】JDK 22的外部函数和内存API无疑是Java编程语言发展史上的一个重要里程碑。它不仅解决了JNI的诸多局限和挑战,还为Java与本地代码的互操作提供了更加高效、安全和简洁的解决方案。随着FFM API的逐渐成熟和完善,我们有理由相信,Java将在更多领域展现出其强大的生命力和竞争力。让我们共同期待Java编程新纪元的到来!
98 11
|
2月前
|
监控 Java 开发者
【并发编程的终极简化】JDK 22结构化并发:让并发编程变得像写代码一样简单!
【9月更文挑战第8天】随着JDK 22的发布,结构化并发为Java编程带来了全新的并发编程体验。它不仅简化了并发编程的复杂性,提高了程序的可靠性和可观察性,还为开发者们提供了更加高效、简单的并发编程方式。我们相信,在未来的发展中,结构化并发将成为Java并发编程的主流方式之一,推动Java编程语言的进一步发展。让我们共同期待Java在并发编程领域的更多创新和突破!
|
3月前
|
存储 Java
【Java集合类面试七】、 JDK7和JDK8中的HashMap有什么区别?
JDK7中的HashMap使用数组加链表解决冲突,而JDK8增加了红黑树结构以优化链表过长时的性能,提高查找效率。
|
4月前
|
存储 安全 Java
Java面试题:请解释Java内存模型,并说明如何在多线程环境下使用synchronized关键字实现同步,阐述ConcurrentHashMap与HashMap的区别,以及它如何在并发环境中提高性能
Java面试题:请解释Java内存模型,并说明如何在多线程环境下使用synchronized关键字实现同步,阐述ConcurrentHashMap与HashMap的区别,以及它如何在并发环境中提高性能
40 0
|
5月前
|
存储 算法 安全
JDK源码分析-HashMap
JDK源码分析-HashMap
下一篇
无影云桌面