7.1——putVal()方法源码分解
public V put(K key, V value) {
//onlyIfAbsent:false 有相应的key和value替换 true有相应的key和value就不能成功写入
return putVal(key, value, false);
}
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
//控制k 和 v 不能为null
if (key == null || value == null) throw new NullPointerException();
//通过spread方法,可以让高位也能参与进寻址运算。
int hash = spread(key.hashCode());
//binCount表示当前k-v 封装成node后插入到指定桶位后,在桶位中的所属链表的下标位置
//0 表示当前桶位为null,node可以直接放着
//2 表示当前桶位已经可能是红黑树
int binCount = 0;
//tab 引用map对象的table
//自旋
for (Node<K,V>[] tab = table;;) {
//f 表示桶位的头结点
//n 表示散列表数组的长度
//i 表示key通过寻址计算后,得到的桶位下标
//fh 表示桶位头结点的hash值
Node<K,V> f; int n, i, fh;
//CASE1:成立,表示当前map中的table尚未初始化..
if (tab == null || (n = tab.length) == 0)
//最终当前线程都会获取到最新的map.table引用。
tab = initTable();
//CASE2:i 表示key使用路由寻址算法得到 key对应 table数组的下标位置,tabAt 获取指定桶位的头结点 f
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//进入到CASE2代码块 前置条件 当前table数组i桶位是Null时。
//使用CAS方式 设置 指定数组i桶位 为 new Node<K,V>(hash, key, value, null),并且期望值是null
//cas操作成功 表示ok,直接break for循环即可
//cas操作失败,表示在当前线程之前,有其它线程先你一步向指定i桶位设置值了。
//当前线程只能再次自旋,去走其它逻辑。
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
//CASE3:前置条件,桶位的头结点一定不是null。
//条件成立表示当前桶位的头结点 为 FWD结点,表示目前map正处于扩容过程中..
else if ((fh = f.hash) == MOVED)
//看到fwd节点后,当前节点有义务帮助当前map对象完成迁移数据的工作
//学完扩容后再来看。
tab = helpTransfer(tab, f);
//CASE4:当前桶位 可能是 链表 也可能是 红黑树代理结点TreeBin
else {
//当插入key存在时,会将旧值赋值给oldVal,返回给put方法调用处..
V oldVal = null;
//使用sync 加锁“头节点”,理论上是“头结点”
synchronized (f) {
//为什么又要对比一下,看看当前桶位的头节点 是否为 之前获取的头结点?
//为了避免其它线程将该桶位的头结点修改掉,导致当前线程从sync 加锁 就有问题了。之后所有操作都不用在做了。
if (tabAt(tab, i) == f) {//条件成立,说明咱们 加锁 的对象没有问题,可以进来造了!
//条件成立,说明当前桶位就是普通链表桶位。
if (fh >= 0) {
//1.当前插入key与链表当中所有元素的key都不一致时,当前的插入操作是追加到链表的末尾,binCount表示链表长度
//2.当前插入key与链表当中的某个元素的key一致时,当前插入操作可能就是替换了。binCount表示冲突位置(binCount - 1)
binCount = 1;
//迭代循环当前桶位的链表,e是每次循环处理节点。
for (Node<K,V> e = f;; ++binCount) {
//当前循环节点 key
K ek;
//条件一:e.hash == hash 成立 表示循环的当前元素的hash值与插入节点的hash值一致,需要进一步判断
//条件二:((ek = e.key) == key ||(ek != null && key.equals(ek)))
// 成立:说明循环的当前节点与插入节点的key一致,发生冲突了
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
//将当前循环的元素的 值 赋值给oldVal
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
//当前元素 与 插入元素的key不一致 时,会走下面程序。
//1.更新循环处理节点为 当前节点的下一个节点
//2.判断下一个节点是否为null,如果是null,说明当前节点已经是队尾了,插入数据需要追加到队尾节点的后面。
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
//前置条件,该桶位一定不是链表
//条件成立,表示当前桶位是 红黑树代理结点TreeBin
else if (f instanceof TreeBin) {
//p 表示红黑树中如果与你插入节点的key 有冲突节点的话 ,则putTreeVal 方法 会返回冲突节点的引用。
Node<K,V> p;
//强制设置binCount为2,因为binCount <= 1 时有其它含义,所以这里设置为了2 回头讲 addCount。
binCount = 2;
//条件一:成立,说明当前插入节点的key与红黑树中的某个节点的key一致,冲突了
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
//将冲突节点的值 赋值给 oldVal
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
//说明当前桶位不为null,可能是红黑树 也可能是链表
if (binCount != 0) {
//如果binCount>=8 表示处理的桶位一定是链表
if (binCount >= TREEIFY_THRESHOLD)
//调用转化链表为红黑树的方法
treeifyBin(tab, i);
//说明当前线程插入的数据key,与原有k-v发生冲突,需要将原数据v返回给调用者。
if (oldVal != null)
return oldVal;
break;
}
}
}
//1.统计当前table一共有多少数据
//2.判断是否达到扩容阈值标准,触发扩容。
//binCount:put》=1 桶位链表长度 =1,表示插入数据和桶位数据的key一致 也可能是也链表的每个Key一致 进行替换
// =0 插入的桶位 为null
// =2 桶位的数据为红黑树
//romove -1L binCount:-1 :删除 不用判断是不是扩容
addCount(1L, binCount);
return null;
}b
7.2——initTable()方法源码分解
map中的table尚未初始化 方法详解
/**
* Initializes table, using the size recorded in sizeCtl.
* * sizeCtl < 0
* * 1. -1 表示当前table正在初始化(有线程在创建table数组),当前线程需要自旋等待..
* * 2.表示当前table数组正在进行扩容 ,高16位表示:扩容的标识戳 低16位表示:(1 + nThread) 当前参与并发扩容的线程数量
* *
* * sizeCtl = 0,表示创建table数组时 使用DEFAULT_CAPACITY为大小
* *
* * sizeCtl > 0
* *
* * 1. 如果table未初始化,表示初始化大小
* * 2. 如果table已经初始化,表示下次扩容时的 触发条件(阈值)
*/
private final Node<K,V>[] initTable() {
//tab 引用map.table
//sc sizeCtl的临时值
Node<K,V>[] tab; int sc;
//自旋 条件:map.table 尚未初始化
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0)
//大概率就是-1,表示其它线程正在进行创建table的过程,当前线程没有竞争到初始化table的锁。
Thread.yield(); // lost initialization race; just spin
//1.sizeCtl = 0,表示创建table数组时 使用DEFAULT_CAPACITY为大小
//2.sizeCtl>0如果table未初始化,表示初始化大小
//3.sizeCtl>0如果table已经初始化,表示下次扩容时的 触发条件(阈值)
// -1 是一把锁 那个线程cas成功就可以进去
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
//这里为什么又要判断呢? 防止其它线程已经初始化完毕了,然后当前线程再次初始化..导致丢失数据。
//条件成立,说明其它线程都没有进入过这个if块,当前线程就是具备初始化table权利了。
if ((tab = table) == null || tab.length == 0) {
//sc大于0 创建table时 使用 sc为指定大小,否则使用 16 默认值.
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
//最终赋值给 map.table
table = tab = nt;
//n >>> 2 => 等于 1/4 n n - (1/4)n = 3/4 n => 0.75 * n
//sc 0.75 n 表示下一次扩容时的触发条件。
sc = n - (n >>> 2);
}
} finally {
//1.如果当前线程是第一次创建map.table的线程话,sc表示的是 下一次扩容的阈值
//2.表示当前线程 并不是第一次创建map.table的线程,当前线程进入到else if 块 时,将
//sizeCtl 设置为了-1 ,那么这时需要将其修改为 进入时的值。
sizeCtl = sc;
}
break;
}
}
return tab;
}