首先HashMap在jdk1.7和1.8之间的实现略有不同。在jdk1.7版本中为了解决hash冲突采用了头插法来形成一个链表。jdk1.8采用了尾插法。接下来分别针对这两种情况来讲解
一、jdk1.7版本
(1)在多线程环境下,如果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;
}
}
}
(2)扩容过程中如果发生了死循环,那么原数组中后续的数据会丢失
二、jdk1.8版本
在jdk1.8中对hashMap进行了优化,在发生hash碰撞,不再采用头插法方式,而是直接插入链表尾部,因此不会出现环形链表的情况,但是在多线程的情况下仍然不安全。
在put时,如果没有hash碰撞则会直接插入元素。假设此时线程A和线程B同时进行put操作,刚好这两条数据对应的hash值一样,并且该位置数据为null,所以线程A,B都会进行直接插入,如果线程A判断该位置为null后还未进行数据插入时挂起,而线程B正常执行,从而正常插入数据,然后线程A获取CPU时间片,此时线程A不用再进行hash判断了,问题出现:线程A会把线程B插入的数据给覆盖,发生线程不安全。