putVal(K key, V value, boolean onlyIfAbsent) 方法
final V putVal(K key, V value, boolean onlyIfAbsent) {
// key、value 不可为空
if (key == null || value == null) throw new NullPointerException();
// 计算hash值
int hash = spread(key.hashCode());
int binCount = 0;
// 遍历HashEntry 数组
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// 如果数组为空,则初始化数组(注意:在初始化时 没有初始化数组,当put时才初始化数
//组)
if (tab == null || (n = tab.length) == 0)
tab = initTable();
// 根据hash值 并于n-1相于 获得数组下标对应Node节点 如果node为空
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
// 无锁添加新节点
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
// f的hash值为-1,表示此时正在扩容
else if ((fh = f.hash) == MOVED)
// 当正在扩容时 协助扩容
tab = helpTransfer(tab, f);
else {
// 到这里 当前key所对应位置的Node不为空,因为刚开始已判断过
V oldVal = null;
// 锁住代码块
synchronized (f) {
// 根据对应的table index 获取对应node 于f(上部分已经赋值)
if (tabAt(tab, i) == f) {
// node 的hash 大于0
if (fh >= 0) {
// 链表初始为1
binCount = 1;
// 遍历Node
for (Node<K,V> e = f;; ++binCount) {
K ek;
// if 当前节点的hash 和put key的hash相同 并且当前节点的key于
//put的key相同(此时是在找是否已存在key,存在则更新即可)
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
// 如果未找到重复的key节点 则将新的节点添加到链表的末尾
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
// 如果发现f的类型是红黑树
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
// 将Node f强转为TreeBin 类型并调用Tree的赋值方法,如果返回值不为null
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
//为oldvalue赋值
oldVal = p.val;
// 如果参数onlyIfAbsent为false 新值替换原有值
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
// 长度不为0
if (binCount != 0) {
// 链表长度大于等于8,链表转红黑树
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
// 原始值不为空 则返回原始值
if (oldVal != null)
return oldVal;
break;
}
}
}
// 这里的作用是判断是否需要扩容
// 计数器这里的1L不是很理解,如果只是更新了值为什么要传1
addCount(1L, binCount);
return null;
}
helpTransfer(Node<K,V>[] tab, Node<K,V> f) 方法
//补充:
//一个特殊的节点,hash值为MOVED,当扩容的线程把原数组的某个位置的节点全部转移后,
//会在这个位置放上ForwardingNode节点,表示该位置已经扩容了,防止put操作插入了错误的位置
//值得一提的是就算正在扩容,put操作发现插入的位置不是ForwardingNode节点,照样可以正常操作
//这表示ConcurrentHashMap可以实现扩容和put的并发操作
//static final class ForwardingNode<K,V> extends Node<K,V>
// 协助扩容
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
Node<K,V>[] nextTab; int sc;
// ForwardingNode 是当数组某个位置的节点全部转移完毕会赋值一个ForwardingNode
// 条件一:数组不为null
// 条件二:节点为FNode节点 ,表示该位置已被转移,put()函数里当调用该方法时是根据MOVED
// 为-1来判断的
// 条件三:FNode扩容后的新table是否为空
if (tab != null && (f instanceof ForwardingNode) &&
(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
// 拿当前标的长度获取扩容标识戳
int rs = resizeStamp(tab.length);
// 条件一:当前下一个要用的table数组于 重组后的相同,true表示当前扩容还未结束,
// false表示已结束,因为扩容完成后的nextTable会设置为null
// 条件二:当前的table数组于传入的table数组 tal相同
// 条件三:sizeCtrl < 0,该值小于0表示已有线程在做初始化操作或扩容操作
while (nextTab == nextTable && table == tab &&
(sc = sizeCtl) < 0) {
// 1、判断扩容标示是否相同,相同表明是同一次扩容,不同则表示非同一此扩容
// 2、true 表示扩容已完毕
// 3、true表示扩容已完毕
// 4、true表示当前Map中的扩容都已完成
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || transferIndex <= 0)
break;
// 协助扩容
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
transfer(tab, nextTab);
break;
}
}
return nextTab;
}
return table;
}