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单线程下的扩容
-
JDK1.7多线程下的扩容:
在JDK1.8及以后,用的ConcurrentHashMap解决死锁的问题,ConcurrentHashMap是线程安全的对死锁问题进行了改进,采用四组指针,分为高位指针和低位指针,hashcode & 数组容量长度,分成两部分迁移,避免头插链表成环。
ConcurrentHashMap 的线程安全机制:CAS + 锁
ConcurrentHashMap保证线程安全的原理思路:
-
T1线程加元