最近在面试中发现,Java集合部分特别喜欢问的三种Map:
HashMap,Hashtable,ConcurrentHashMap
那么,下面就以我的理解,讲讲上面三个Map集合,如果有什么不对尽请指正,谢谢
HashMap
性质:
扩容指数:0.75,初始容量16,每次达到容量的0.75,也就是四分之三的时候触发函数 rehash() 进行扩容,允许null键null值,线程不安全。
特别要注意的是,HashMap为什么不支持线程安全:
假如此时HashMap的容量为16,共有12个元素,那么可以知道,我们已经达到扩容的指数,下一次即将扩容,此时:
如果多个线程同时对HashMap进行put元素,那么同时会进入 rehash() 函数内部,那么此时就会出现HashMap成环的情况。
为什么会出现成环的情况?源码如下:
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;
}
}
}
先解释一下该函数的过程:
首先获取新表的长度,之后遍历新表的每一个entry,然后每个ertry中的链表,以反转的形式,形成rehash之后的链表。(HashMap存储的数据结构Node<K,V>就是继承于Entry)
/** * Basic hash bin node, used for most entries. (See below for * TreeNode subclass, and in LinkedHashMap for its Entry subclass.) */
static class Node<K,V> implements Map.Entry<K,V> {
//.......
}
并发问题:
若当前线程此时获得ertry节点,但是被线程中断无法继续执行,此时线程二进入transfer函数,并把函数顺利执行,此时新表中的某个位置有了节点,之后线程一获得执行权继续执行,因为并发transfer。
所以两者都是扩容的同一个链表,当线程一执行到e.next = new table[i] 的时候,由于线程二之前数据迁移的原因导致此时new table[i] 上就有ertry存在,所以线程一执行的时候,会将next节点,设置为自己,导致自己互相使用next引用对方,因此产生链表,导致死循环。
简单来说,就是当第一个线程把扩容完成之时,如果第二个线程正在扩容途中,那么会将next节点设置为自己本身,导致链表成环了。
总结
HashMap在jdk1.7的时候,使用数组+链表,链表用处是来解决Hash冲突的,在插入链表的时候,采用了头插法,但是头插会引起多线程并发问题。
HashMap在jdk1.8的时候,使用了数组+链表/红黑树,链表长度为8的时候,转化为红黑树,长度为6的时候,转回红黑树,8的意思是此时红黑树的使用和查询效率优于链表。
为什么6的时候转回,而不是8?放置频繁转化
jdk1.8的时候HashMap使用尾插法,不会出现并发成环的情况,但是并发会出现数据丢失的情况。
网友评论