以 JDK8 的 HashMap 為例码撰。
1??情況一:
線程甲和線程乙共同對(duì) HashMap 進(jìn)行 put 操作。假設(shè)甲乙插入的 Key-Value 中 key 的 hashcode 是相同的嗜历,這說(shuō)明該鍵值對(duì)將會(huì)插入到 Table 的同一個(gè)下標(biāo)的位置抒线,會(huì)發(fā)生哈希碰撞胰挑,此時(shí) HashMap 按照平時(shí)的做法是形成一個(gè)鏈表(若超過(guò)八個(gè)節(jié)點(diǎn)則是紅黑樹)∮绶剩現(xiàn)在插入的下標(biāo)為 null(Table[i]==null) 則進(jìn)行正常的插入,此時(shí)線程甲進(jìn)行到了這一步正準(zhǔn)備插入训裆,被堵塞眶根,線程乙獲得運(yùn)行時(shí)間,進(jìn)行同樣操作边琉,也是 Table[i]==null属百,此時(shí)它直接運(yùn)行完整個(gè) put 方法,成功將元素插入变姨。隨后線程甲獲得運(yùn)行時(shí)間接著上面的判斷繼續(xù)運(yùn)行族扰,進(jìn)行了 Table[i]==null 的插入(此時(shí)實(shí)際是 Table[i]!=null 的操作,因?yàn)榍懊婢€程乙已經(jīng)插入元素了)定欧,這樣就會(huì)直接覆蓋線程乙插入的數(shù)據(jù)别伏,如此非線程安全。在 hashmap 做 put 操作的時(shí)候會(huì)調(diào)用到以下的方法:
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}
甲乙線程同時(shí)對(duì)同一個(gè)數(shù)組位置調(diào)用 addEntry忧额,兩個(gè)線程會(huì)同時(shí)得到現(xiàn)在的頭結(jié)點(diǎn),然后甲寫入新的頭結(jié)點(diǎn)之后愧口,乙也寫入新的頭結(jié)點(diǎn)睦番,那乙的寫入操作就會(huì)覆蓋甲的寫入操作造成甲的寫入操作丟失。
2??情況二:
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;
}
}
}
這種情況是 resize 的時(shí)候造成的。假設(shè) HashMap 中的 Table 情況如下: