一、put時存在丟失元素的問題
put方法邏輯說明
將新插入的元素放置到鏈表頭部,原來的鏈表頭部作爲新元素的next節點
put關鍵代碼塊
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex]; // 步驟1
table[bucketIndex] = new Entry<>(hash, key, value, e); // 步驟2
size++;
}
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
丟失元素分析
當多線程put && key不相同 && key的hash值存在衝突(hash值相同或者hash值除以數組長度後的餘數相同)時,即往同一個鏈表中插入時,由於沒有加同步鎖,假若線程a,b執行的步驟如下:
線程a 步驟1
線程b 步驟1
線程a 步驟2
線程b 步驟2
那麼線程a put的元素將會丟失
二、resize時存在死循環的問題
resize方法邏輯說明
分析代碼可以看出,擴容時會將結點倒序(原來的1個鏈表會拆分成2個,並且都會倒序)
resize關鍵代碼塊
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable, initHashSeedAsNeeded(newCapacity)); // 步驟1
table = newTable; // 步驟2
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
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;
}
}
}
死循環分析
假若hashmap中的一個槽位x上已經有一個鏈表,元素依次爲(1,1)->(2,2)->(3,3)->(4,4)>(5,5)->(6,6) ,其中1在槽位x上,
理論上擴容後
槽位x : (5,5)->(3,3)->(1,1)
槽位2x : (6,6)->(4,4)->(2,2)
線程a,線程b 現在都要插入一個元素(7,7)時,檢測發現都需要擴容,假若線程a,b執行的步驟如下:
線程a 步驟1 步驟1全部執行完成,這個時候newTable已經完成,元素已經倒序過來了 (5,5)->(3,3)->(1,1) && (6,6)->(4,4)->(2,2) ,可以看到(3,3)的next節點爲(1,1)
線程b 步驟1 步驟1進行中, 遍歷舊鏈表 (1,1)->(2,2)->(3,3) 接着再往後遍歷時就又到(1,1),從而進入了死循環
線程a 步驟2
線程b 步驟2
參考文章
https://blog.csdn.net/maohoo/article/details/81531925
jdk8之後依舊還是存在死循環的問題
看代碼情況只可能是兩個紅黑樹節點的父親節點相互引用纔可以導致無法走出這個for語句。
[當另外一個線程put元素後,可能需要左旋或者右旋造成的臨時狀態]
final TreeNode<K,V> root() {
for (TreeNode<K,V> r = this, p;;) {
if ((p = r.parent) == null)
return r;
r = p;
}
}
參考文章
https://blog.csdn.net/qq_33330687/article/details/101479385