put操作大致可分爲以下幾個步驟:
1·計算key的hash值,即調用spread()方法計算hash值;
2·獲取hash值對應的Node節點位置,此時通過一個循環實現。有以下幾種情況:
3·A`如果table表爲空,則首先進行初始化操作,初始化之後再次進入循環獲取Node節點的位置;
3·B`如果table不爲空,但沒有找到key對應的Node節點,則直接調用casTabAt()方法插入一個新節點,此時使用CAS進行插入,
CAS失敗,說明有其它線程提前插入了,CAS會嘗試自旋重新插入;
,因爲第一次CAS失敗,CAS發現數據與原數據不同,會自旋重新插入,第二次進入修改數據爲想要插入的數據
CAS成功,則直接插入並計算addCount,判斷是否需要把鏈表升級爲紅黑樹。
3·C`如果table不爲空,且key對應的Node節點也不爲空,但Node頭節點點的hash值爲MOVED(-1)(hash值默認爲MOVED(-1)),則表示需要擴容,此時調用helpTransfer()方法進行擴容;(第五步補充:如果頭節點的hash值爲-1,說明當前f是ForwardingNode節點,意味有其它線程正在擴容,則一起進行擴容操作。我的理解是第一次擴容,頭節點才爲-1
3·D`其他情況下,則直接向Node中插入一個新Node節點,此時需要對這個Node鏈表或紅黑樹通過synchronized加鎖。
插入元素後,調用addCount()方法記錄table中元素的數量,判斷對應的Node結構是否需要改變結構,如果需要則調用treeifyBin()方法將Node鏈表升級爲紅黑樹結構;
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
put時,內部使用的方法
//以volatile讀的方式讀取table數組中的元素
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
//以volatile寫的方式,將元素插入table數組(修改數據使用)
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}
//以CAS的方式,將元素插入table數組(上述步驟3·B)
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
Node<K,V> c, Node<K,V> v) {
//原子的執行如下邏輯:如果tab[i]==c,則設置tab[i]=v,並返回ture.否則返回false
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
————————————————
推薦鏈接:JDK1.8,concurrethashmap詳解
最佳推薦:https://www.jianshu.com/p/5bc70d9e5410
可以參考https://www.jianshu.com/p/c0642afe03e0