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