首先HashMap在jdk1.7和1.8之間的實現(xiàn)略有不同们镜。在jdk1.7版本中為了解決hash沖突采用了頭插法來形成一個鏈表硝逢。jdk1.8采用了尾插法姨拥。接下來分別針對這兩種情況來講解
一、jdk1.7版本
(1)在多線程環(huán)境下渠鸽,如果hashmap是一個共享資源叫乌,那么在擴容的情況下,其會出現(xiàn)死循環(huán)徽缚。
其中頭插法的部分代碼:
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)擴容過程中如果發(fā)生了死循環(huán)憨奸,那么原數(shù)組中后續(xù)的數(shù)據(jù)會丟失
二、jdk1.8版本
在jdk1.8中對hashMap進行了優(yōu)化凿试,在發(fā)生hash碰撞排宰,不再采用頭插法方式,而是直接插入鏈表尾部那婉,因此不會出現(xiàn)環(huán)形鏈表的情況额各,但是在多線程的情況下仍然不安全。
在put時吧恃,如果沒有hash碰撞則會直接插入元素。假設(shè)此時線程A和線程B同時進行put操作麻诀,剛好這兩條數(shù)據(jù)對應(yīng)的hash值一樣痕寓,并且該位置數(shù)據(jù)為null,所以線程A,B都會進行直接插入蝇闭,如果線程A判斷該位置為null后還未進行數(shù)據(jù)插入時掛起呻率,而線程B正常執(zhí)行,從而正常插入數(shù)據(jù)呻引,然后線程A獲取CPU時間片礼仗,此時線程A不用再進行hash判斷了,問題出現(xiàn):線程A會把線程B插入的數(shù)據(jù)給覆蓋逻悠,發(fā)生線程不安全元践。