Java Map中的核心特性——扩容、初始化与懒加载、哈希计算、位运算、并发

本文详细探讨了Java Map中的核心特性,包括自动扩容、初始化与懒加载、哈希计算和位运算。在扩容过程中,JDK1.8之前的HashMap存在并发死锁问题,JDK1.8通过位运算和红黑树解决了这个问题。初始化时,HashMap在首次put时才会进行初始化。哈希计算中,JDK使用扰动函数降低哈希冲突。位运算是确定哈希桶大小的关键。在并发处理上,HashMap经历了从全表锁到分段锁再到乐观锁的演进,以提高并发性能。
摘要由CSDN通过智能技术生成

Map中的集合核心特性

自动扩容

​ 最小可用原则,容量超过一定阈值便自动进行扩容。

  • 扩容是通过resize方法来实现的。扩容发生在putVal方法的最后,即写入元素之后才会判断是否需要扩容操作,当自增后的size大于之前所计算好的阈值threshold,即执行resize操作。

    图片

  • 通过位运算<<1进行容量扩充,即扩容1倍,同时新的阈值newThr也扩容为老阈值的1倍

    图片

    扩容时,总共存在三个问题

    • 哈希桶数组中某个位置只有1个元素,即不存在哈希冲突时,则直接将该元素copy至新哈希桶数组的对应位置即可。

    • 哈希桶数组中某个位置的节点为树节点时,则执行红黑树的扩容操作。

    • 哈希桶数组中某个位置的节点为普通节点时,则执行链表扩容操作,在JDK1.8中,为了避免之前版本中并发扩容所导致的死链问题,引入了高低位链表辅助进行扩容操作

    图片

    针对扩容时出现的问题的解答

为什么JDK1.7扩容时会产生并发死锁问题,也就是我们常温的为什么hashmap是线程不安全的?

​ 主要原因是:并发,即多线程同时访问HashMap

​ hashmap是线程不安全造成的影响主要有两个方面:1 、高并发下,hashmap会出现扩容的死锁问题;2、数据会丢失,会造成数据的脏读

怎么产生的环形链表死循环问题?

​ JDK1.7 使用的是数组+单链表的数据结构会先进行扩容再插入,执行的是头插法,先将原位置的数据移到后一位,再插入数据到该位置;会出现逆序和环形链表死循环问题

​ JDK1.8 使用的是数组+链表+红黑树的数据结构(当链表的深度达到8的时候,也就是默认阈值,就会自动扩容把链表转成红黑树的数据结构来把时间复杂度从O(n)变成O(logN)提高了效率),会先进行插入再进行扩容,执行的是尾插法,直接插到链表尾部/红黑数树,不会出现逆序和环形链表死循环问题

线程不安全是因为:数组确定了就不会修改,想扩容则申请新的数组,把老数据进行迁移,涉及的源码:Entry[] newTable = new Entry[newCapacity]; 当迁移时,单线程迁移没有问题,当有多个线程需要扩容时,都申请了数组,则可能数据迁移不成功,造成JVM内存溢出,并造成大量GC,则当线程迁移时,造成死锁。

​ 在扩容时,在哈希冲突的时候,产生的链表形成环,当有key在成环链表中时,则成死循环。

  • JDK1.7单线程下的扩容

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fmXapgbo-1617181681474)(D:\chencan\img\blog_gif\test_1.gif)]

  • JDK1.7多线程下的扩容:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WDRa5MGK-1617181681476)(D:\chencan\img\blog_gif\test_2.gif)]

​ 在JDK1.8及以后,用的ConcurrentHashMap解决死锁的问题,ConcurrentHashMap是线程安全的对死锁问题进行了改进,采用四组指针,分为高位指针和低位指针,hashcode & 数组容量长度,分成两部分迁移,避免头插链表成环。

ConcurrentHashMap 的线程安全机制:CAS + 锁

​ ConcurrentHashMap保证线程安全的原理思路:

  • T1线程加元

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值