put()
package leetcode0606.JUC.ConcurrentHashMap;
import java.util.concurrent.ConcurrentHashMap;
public class _put {
public V put(K key, V value) {
return putVal(key, value, false);
}
// 核心方法
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
// key value 不能为null
if (key == null || value == null) throw new NullPointerException();
// 调用扰动哈希函数
int hash = spread(key.hashCode());
// binCount 表示 k-v 封装为Node ,插入到指定桶位后,在桶位中所属链表的下标位置
/*
0 --- 当前桶位 为null,node可以直接放入
2 --- 当前 桶位 可能为红黑树
*/
int binCount = 0;
// 自旋
for (ConcurrentHashMap.Node<K,V>[] tab = table;;) {
// f 某个桶位的头节点
// n table长度
// i 桶位下标
// fh 头节点的哈希值
ConcurrentHashMap.Node<K,V> f; int n, i, fh;
// CASE1 table 表未初始化
if (tab == null || (n = tab.length) == 0)
// 多个线程并发地初始化,拿到tab,重新自旋
tab = initTable();
// CASE2 table已经初始化了,指定位置为null,可尝试插入
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
// 前置条件:当前桶位为null,多个线程进行竞争地CAS操作
if (casTabAt(tab, i, null,
new ConcurrentHashMap.Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
/*
头节点 不为空
*/
// CASE3 头节点 是一个 FWD 节点 , 即正在扩容中,此线程有义务协助扩容
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
// CASE4 当前桶位 可能是链表 也可能是 就红黑树代理节点 treeBin
else {
// 当插入key存在时,会将 旧值 赋给 oldVal,并返回。
V oldVal = null;
// f 头节点
synchronized (f) {
// 为了防止头节点被修改了,如果被修改了,锁都加错了
if (tabAt(tab, i) == f) {
// fh >= 0 表示当前桶为 普通链表
if (fh >= 0) {
/*
binCount = 1
当前插入key与链表当中所有key都不一样,将插入元素追加到末尾,binCount表示链表长度
当前插入key与链表当中所有key有冲突,替换value,binCount-1 表示冲突位置
*/
binCount = 1;
for (ConcurrentHashMap.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;
}
// 未发生冲突,遍历链表
ConcurrentHashMap.Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new ConcurrentHashMap.Node<K,V>(hash, key,
value, null);
break;
}
}
}
// 前置条件: f 桶位不是链表
else if (f instanceof ConcurrentHashMap.TreeBin) {
ConcurrentHashMap.Node<K,V> p;
// 由于binCount <=1 有特殊含义,所以设置为2
binCount = 2;
// 条件成立:当前插入节点与红黑树某个节点发生冲突。
if ((p = ((ConcurrentHashMap.TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
// 新值代替旧值
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
// 说明当前桶位不为null,可能是链表,也可能是红黑树
if (binCount != 0) {
// 如果 binCount >=8 ,表示处理的桶位一定是链表,需要执行树化的方法。
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
// 插入数据时发生冲突,返回旧值
if (oldVal != null)
return oldVal;
break;
}
}
}
// addCount的作用
// 1.统计当前table一共有多少数据
// 2.判断是否达到扩容阈值标准,触发扩容
addCount(1L, binCount);
return null;
}
}
initTable()
package leetcode0606.JUC.ConcurrentHashMap;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
public class _initTable {
// 多线程场景下地初始化
private final ConcurrentHashMap.Node<K,V>[] initTable() {
// tab 表示 table的引用
// sc 表示 sizeCtl 的引用
ConcurrentHashMap.Node<K,V>[] tab; int sc;
// 循坏的条件是 当前的table未初始化
while ((tab = table) == null || tab.length == 0) {
/*
sizeCtl < 0 大概率是-1
-1 表示table正在初始化(有其它线程在创建table数组),当前线程需要自旋等待
sizeCtl = 0
表示创建table数组时,使用 DEFAULT_CAPACITY大小
sizeCtl > 0
如果table未初始化,表示初始化大小 ,如果调用有参构造器,其中有这么一句 this.sizeCtl = cap;
如果调用无参的那么sizeCtl = 0,走默认长度 -----int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
如果已经初始化,表示下次扩容时的 触发条件(阈值)
*/
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
// sizeCtl=-1 可以看成是一把锁,谁抢到它,谁就初始化数组
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
// 防止多线程 重复初始化,丢失数据
// 若条件成立,说明 此线程是第一个初始化数组的
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
ConcurrentHashMap.Node<K,V>[] nt = (ConcurrentHashMap.Node<K,V>[])new ConcurrentHashMap.Node<?,?>[n];
table = tab = nt;
// sc = 3/4 * n = 0.75*16 = 12 ; 即为下次扩容的阈值
sc = n - (n >>> 2);
}
} finally {
/*
CASE1 若是第一次初始化,则sizeCtl 是下一次扩容的阈值
CASE2 若已经完成初始化,这个线程仍然可以进行CAS,将sizeCtl改成-1,还要把sizeCtl改回去。
*/
sizeCtl = sc;
}
break;
}
}
return tab;
}
}