11_小菜一碟之get(k)方法 源码分解
CONCURRENT中的get方法
public V get(Object key) {
//tab 引用map.table
//e 当前元素
//p 目标节点
//n table数组长度
//eh 当前元素hash
//ek 当前元素key
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
//扰动运算后得到 更散列的hash值
int h = spread(key.hashCode());
//条件一:(tab = table) != null
//true->表示已经put过数据,并且map内部的table也已经初始化完毕
//false->表示创建完map后,并没有put过数据,map内部的table是延迟初始化的,只有第一次写数据时会触发创建逻辑。
//条件二:(n = tab.length) > 0 true->表示table已经初始化
//条件三:(e = tabAt(tab, (n - 1) & h)) != null
//true->当前key寻址的桶位 有值
//false->当前key寻址的桶位中是null,是null直接返回null
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
//前置条件:当前桶位有数据
//对比头结点hash与查询key的hash是否一致
//条件成立:说明头结点与查询Key的hash值 完全一致
if ((eh = e.hash) == h) {
//完全比对 查询key 和 头结点的key
//条件成立:说明头结点就是查询数据
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
//条件成立:
//1.-1 fwd 说明当前table正在扩容,且当前查询的这个桶位的数据 已经被迁移走了
//2.-2 TreeBin节点,需要使用TreeBin 提供的find 方法查询。
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
//当前桶位已经形成链表的这种情况
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
然后就来到了ForwardingNode的find(int h, Object k)
Node<K,V> find(int h, Object k) {
// loop to avoid arbitrarily deep recursion on forwarding nodes
//tab 一定不为空
Node<K,V>[] tab = nextTable;
outer: for (;;) {
//n 表示为扩容而创建的 新表的长度
//e 表示在扩容而创建新表使用 寻址算法 得到的 桶位头结点
Node<K,V> e; int n;
//条件一:永远不成立
//条件二:永远不成立
//条件三:永远不成立
//条件四:在新扩容表中 重新定位 hash 对应的头结点
//true -> 1.在oldTable中 对应的桶位在迁移之前就是null
// 2.扩容完成后,有其它写线程,将此桶位设置为了null
if (k == null || tab == null || (n = tab.length) == 0 ||
(e = tabAt(tab, (n - 1) & h)) == null)
return null;
//前置条件:扩容后的表 对应hash的桶位一定不是null,e为此桶位的头结点
//e可能为哪些node类型?
//1.node 类型
//2.TreeBin 类型
//3.FWD 类型
for (;;) {
//eh 新扩容后表指定桶位的当前节点的hash
//ek 新扩容后表指定桶位的当前节点的key
int eh; K ek;
//条件成立:说明新扩容 后的表,当前命中桶位中的数据,即为 查询想要数据。
if ((eh = e.hash) == h &&
((ek = e.key) == k || (ek != null && k.equals(ek))))
return e;
//eh<0
//1.TreeBin 类型 2.FWD类型(新扩容的表,在并发很大的情况下,可能在此方法 再次拿到FWD类型..)
if (eh < 0) {
if (e instanceof ForwardingNode) {
tab = ((ForwardingNode<K,V>)e).nextTable;
continue outer;
}
else
//说明此桶位 为 TreeBin 节点,使用TreeBin.find 查找红黑树中相应节点。
return e.find(h, k);
}
//前置条件:当前桶位头结点 并没有命中查询,说明此桶位是 链表
//1.将当前元素 指向链表的下一个元素
//2.判断当前元素的下一个位置 是否为空
// true->说明迭代到链表末尾,未找到对应的数据,返回Null
if ((e = e.next) == null)
return null;
}
}
}
12_核心方法之remove()方法源码分解(重点)
public V remove(Object key) {
return replaceNode(key, null, null);
}
/**
* Implementation for the four public remove/replace methods:
* Replaces node value with v, conditional upon match of cv if
* non-null. If resulting value is null, delete.
*/
final V replaceNode(Object key, V value, Object cv) {
//计算key经过扰动运算后的hash
int hash = spread(key.hashCode());
//自旋
for (Node<K,V>[] tab = table;;) {
//f表示桶位头结点
//n表示当前table数组长度
//i表示hash命中桶位下标
//fh表示桶位头结点 hash
Node<K,V> f; int n, i, fh;
//CASE1:
//条件一:tab == null true->表示当前map.table尚未初始化.. false->已经初始化
//条件二:(n = tab.length) == 0 true->表示当前map.table尚未初始化.. false->已经初始化
//条件三:(f = tabAt(tab, i = (n - 1) & hash)) == null true -> 表示命中桶位中为null,直接break, 会返回
if (tab == null || (n = tab.length) == 0 ||
(f = tabAt(tab, i = (n - 1) & hash)) == null)
break;
//CASE2:
//前置条件CASE2 ~ CASE3:当前桶位不是null
//条件成立:说明当前table正在扩容中,当前是个写操作,所以当前线程需要协助table完成扩容。
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
//CASE3:
//前置条件CASE2 ~ CASE3:当前桶位不是null
//当前桶位 可能是 "链表" 也可能 是 "红黑树" TreeBin
else {
//保留替换之前的数据引用
V oldVal = null;
//校验标记
boolean validated = false;
//加锁当前桶位 头结点,加锁成功之后会进入 代码块。
synchronized (f) {
//判断sync加锁是否为当前桶位 头节点,防止其它线程,在当前线程加锁成功之前,修改过 桶位 的头结点。
//条件成立:当前桶位头结点 仍然为f,其它线程没修改过。
if (tabAt(tab, i) == f) {
//条件成立:说明桶位 为 链表 或者 单个 node
if (fh >= 0) {
validated = true;
//e 表示当前循环处理元素
//pred 表示当前循环节点的上一个节点
Node<K,V> e = f, pred = null;
for (;;) {
//当前节点key
K ek;
//条件一:e.hash == hash true->说明当前节点的hash与查找节点hash一致
//条件二:((ek = e.key) == key || (ek != null && key.equals(ek)))
//if 条件成立,说明key 与查询的key完全一致。
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
//当前节点的value
V ev = e.val;
//条件一:cv == null true->替换的值为null 那么就是一个删除操作
//条件二:cv == ev || (ev != null && cv.equals(ev)) 那么是一个替换操作
if (cv == null || cv == ev ||
(ev != null && cv.equals(ev))) {
//删除 或者 替换
//将当前节点的值 赋值给 oldVal 后续返回会用到
oldVal = ev;
//条件成立:说明当前是一个替换操作
if (value != null)
//直接替换
e.val = value;
//条件成立:说明当前节点非头结点
else if (pred != null)
//当前节点的上一个节点,指向当前节点的下一个节点。
pred.next = e.next;
else
//说明当前节点即为 头结点,只需要将 桶位设置为头结点的下一个节点。
setTabAt(tab, i, e.next);
}
break;
}
pred = e;
if ((e = e.next) == null)
break;
}
}
//条件成立:TreeBin节点。
else if (f instanceof TreeBin) {
validated = true;
//转换为实际类型 TreeBin t
TreeBin<K,V> t = (TreeBin<K,V>)f;
//r 表示 红黑树 根节点
//p 表示 红黑树中查找到对应key 一致的node
TreeNode<K,V> r, p;
//条件一:(r = t.root) != null 理论上是成立
//条件二:TreeNode.findTreeNode 以当前节点为入口,向下查找key(包括本身节点)
// true->说明查找到相应key 对应的node节点。会赋值给p
if ((r = t.root) != null &&
(p = r.findTreeNode(hash, key, null)) != null) {
//保存p.val 到pv
V pv = p.val;
//条件一:cv == null 成立:不比对value,就做替换或者删除操作
//条件二:cv == pv ||(pv != null && cv.equals(pv)) 成立:说明“对比值”与当前p节点的值 一致
if (cv == null || cv == pv ||
(pv != null && cv.equals(pv))) {
//替换或者删除操作
oldVal = pv;
//条件成立:替换操作
if (value != null)
p.val = value;
//删除操作
else if (t.removeTreeNode(p))
//这里没做判断,直接搞了...很疑惑
setTabAt(tab, i, untreeify(t.first));
}
}
}
}
}
//当其他线程修改过桶位 头结点时,当前线程 sync 头结点 锁错对象时,validated 为false,会进入下次for 自旋
if (validated) {
if (oldVal != null) {
//替换的值 为null,说明当前是一次删除操作,oldVal !=null 成立,说明删除成功,更新当前元素个数计数器。
if (value == null)
addCount(-1L, -1);
return oldVal;
}
break;
}
}
}
return null;
} public V remove(Object key) {
return replaceNode(key, null, null);
}
/**
* Implementation for the four public remove/replace methods:
* Replaces node value with v, conditional upon match of cv if
* non-null. If resulting value is null, delete.
*/
final V replaceNode(Object key, V value, Object cv) {
//计算key经过扰动运算后的hash
int hash = spread(key.hashCode());
//自旋
for (Node<K,V>[] tab = table;;) {
//f表示桶位头结点
//n表示当前table数组长度
//i表示hash命中桶位下标
//fh表示桶位头结点 hash
Node<K,V> f; int n, i, fh;
//CASE1:
//条件一:tab == null true->表示当前map.table尚未初始化.. false->已经初始化
//条件二:(n = tab.length) == 0 true->表示当前map.table尚未初始化.. false->已经初始化
//条件三:(f = tabAt(tab, i = (n - 1) & hash)) == null true -> 表示命中桶位中为null,直接break, 会返回
if (tab == null || (n = tab.length) == 0 ||
(f = tabAt(tab, i = (n - 1) & hash)) == null)
break;
//CASE2:
//前置条件CASE2 ~ CASE3:当前桶位不是null
//条件成立:说明当前table正在扩容中,当前是个写操作,所以当前线程需要协助table完成扩容。
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
//CASE3:
//前置条件CASE2 ~ CASE3:当前桶位不是null
//当前桶位 可能是 "链表" 也可能 是 "红黑树" TreeBin
else {
//保留替换之前的数据引用
V oldVal = null;
//校验标记
boolean validated = false;
//加锁当前桶位 头结点,加锁成功之后会进入 代码块。
synchronized (f) {
//判断sync加锁是否为当前桶位 头节点,防止其它线程,在当前线程加锁成功之前,修改过 桶位 的头结点。
//条件成立:当前桶位头结点 仍然为f,其它线程没修改过。
if (tabAt(tab, i) == f) {
//条件成立:说明桶位 为 链表 或者 单个 node
if (fh >= 0) {
validated = true;
//e 表示当前循环处理元素
//pred 表示当前循环节点的上一个节点
Node<K,V> e = f, pred = null;
for (;;) {
//当前节点key
K ek;
//条件一:e.hash == hash true->说明当前节点的hash与查找节点hash一致
//条件二:((ek = e.key) == key || (ek != null && key.equals(ek)))
//if 条件成立,说明key 与查询的key完全一致。
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
//当前节点的value
V ev = e.val;
//条件一:cv == null true->替换的值为null 那么就是一个删除操作
//条件二:cv == ev || (ev != null && cv.equals(ev)) 那么是一个替换操作
if (cv == null || cv == ev ||
(ev != null && cv.equals(ev))) {
//删除 或者 替换
//将当前节点的值 赋值给 oldVal 后续返回会用到
oldVal = ev;
//条件成立:说明当前是一个替换操作
if (value != null)
//直接替换
e.val = value;
//条件成立:说明当前节点非头结点
else if (pred != null)
//当前节点的上一个节点,指向当前节点的下一个节点。
pred.next = e.next;
else
//说明当前节点即为 头结点,只需要将 桶位设置为头结点的下一个节点。
setTabAt(tab, i, e.next);
}
break;
}
pred = e;
if ((e = e.next) == null)
break;
}
}
//条件成立:TreeBin节点。
else if (f instanceof TreeBin) {
validated = true;
//转换为实际类型 TreeBin t
TreeBin<K,V> t = (TreeBin<K,V>)f;
//r 表示 红黑树 根节点
//p 表示 红黑树中查找到对应key 一致的node
TreeNode<K,V> r, p;
//条件一:(r = t.root) != null 理论上是成立
//条件二:TreeNode.findTreeNode 以当前节点为入口,向下查找key(包括本身节点)
// true->说明查找到相应key 对应的node节点。会赋值给p
if ((r = t.root) != null &&
(p = r.findTreeNode(hash, key, null)) != null) {
//保存p.val 到pv
V pv = p.val;
//条件一:cv == null 成立:不比对value,就做替换或者删除操作
//条件二:cv == pv ||(pv != null && cv.equals(pv)) 成立:说明“对比值”与当前p节点的值 一致
if (cv == null || cv == pv ||
(pv != null && cv.equals(pv))) {
//替换或者删除操作
oldVal = pv;
//条件成立:替换操作
if (value != null)
p.val = value;
//删除操作
else if (t.removeTreeNode(p))
//这里没做判断,直接搞了...很疑惑
setTabAt(tab, i, untreeify(t.first));
}
}
}
}
}
//当其他线程修改过桶位 头结点时,当前线程 sync 头结点 锁错对象时,validated 为false,会进入下次for 自旋
if (validated) {
if (oldVal != null) {
//替换的值 为null,说明当前是一次删除操作,oldVal !=null 成立,说明删除成功,更新当前元素个数计数器。
if (value == null)
addCount(-1L, -1);
return oldVal;
}
break;
}
}
}
return null;
}
13_ConcurrentHashMap内部类之TreeBin分解(重点中的重点!!)
13.1——TreeBin有什么作用?
TreeBin节点添加元素时候的写锁的获取和释放
13.2——TreeBin成员变量分析
static final class TreeBin<K,V> extends Node<K,V> {
//红黑树 根节点
TreeNode<K,V> root;
//链表的头节点
volatile TreeNode<K,V> first;
//等待者线程(当前lockState是读锁状态)
volatile Thread waiter;
/**
* 1.写锁状态 写是独占状态,以散列表来看,真正进入到TreeBin中的写线程 同一时刻 只有一个线程。 1
* 2.读锁状态 读锁是共享,同一时刻可以有多个线程 同时进入到 TreeBin对象中获取数据。 每一个线程 都会给 lockStat + 4
* 3.等待者状态(写线程在等待),当TreeBin中有读线程目前正在读取数据时,写线程无法修改数据,那么就将lockState的最低2位 设置为 0b 10
*/
volatile int lockState;
// values for lockState
static final int WRITER = 1; // set while holding write lock
static final int WAITER = 2; // set when waiting for write lock
static final int READER = 4; // increment value for setting read lock
13.3——TreeBin构造方法源码分析
TreeBin(TreeNode<K,V> b) {
//设置节点hash为-2 表示此节点是TreeBin节点
super(TREEBIN, null, null, null);
//使用first 引用 treeNode链表
this.first = b;
//r 红黑树的根节点引用
TreeNode<K,V> r = null;
//x表示遍历的当前节点
for (TreeNode<K,V> x = b, next; x != null; x = next) {
next = (TreeNode<K,V>)x.next;
//强制设置当前插入节点的左右子树为null
x.left = x.right = null;
//条件成立:说明当前红黑树 是一个空树,那么设置插入元素 为根节点
if (r == null) {
//根节点的父节点 一定为 null
x.parent = null;
//颜色改为黑色
x.red = false;
//让r引用x所指向的对象。
r = x;
}
else {
//非第一次循环,都会来带else分支,此时红黑树已经有数据了
//k 表示 插入节点的key
K k = x.key;
//h 表示 插入节点的hash
int h = x.hash;
//kc 表示 插入节点key的class类型
Class<?> kc = null;
//p 表示 为查找插入节点的父节点的一个临时节点
TreeNode<K,V> p = r;
for (;;) {
//dir (-1, 1)
//-1 表示插入节点的hash值大于 当前p节点的hash
//1 表示插入节点的hash值 小于 当前p节点的hash
//ph p表示 为查找插入节点的父节点的一个临时节点的hash
int dir, ph;
//临时节点 key
K pk = p.key;
//插入节点的hash值 小于 当前节点
if ((ph = p.hash) > h)
//插入节点可能需要插入到当前节点的左子节点 或者 继续在左子树上查找
dir = -1;
//插入节点的hash值 大于 当前节点
else if (ph < h)
//插入节点可能需要插入到当前节点的右子节点 或者 继续在右子树上查找
dir = 1;
//如果执行到 CASE3,说明当前插入节点的hash 与 当前节点的hash一致,会在case3 做出最终排序。最终
//拿到的dir 一定不是0,(-1, 1)
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
dir = tieBreakOrder(k, pk);
//xp 想要表示的是 插入节点的 父节点
TreeNode<K,V> xp = p;
//条件成立:说明当前p节点 即为插入节点的父节点
//条件不成立:说明p节点 底下还有层次,需要将p指向 p的左子节点 或者 右子节点,表示继续向下搜索。
if ((p = (dir <= 0) ? p.left : p.right) == null) {
//设置插入节点的父节点 为 当前节点
x.parent = xp;
//小于P节点,需要插入到P节点的左子节点
if (dir <= 0)
xp.left = x;
//大于P节点,需要插入到P节点的右子节点
else
xp.right = x;
//插入节点后,红黑树性质 可能会被破坏,所以需要调用 平衡方法
r = balanceInsertion(r, x);
break;
}
}
}
}
//将r 赋值给 TreeBin对象的 root引用。
this.root = r;
assert checkInvariants(root);
}
13.4——TreeBin核心方法putTreeVal方法源码分析
final TreeNode<K,V> putTreeVal(int h, K k, V v) {
Class<?> kc = null;
boolean searched = false;
for (TreeNode<K,V> p = root;;) {
int dir, ph; K pk;
if (p == null) {
first = root = new TreeNode<K,V>(h, k, v, null, null);
break;
}
else if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
return p;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0) {
if (!searched) {
TreeNode<K,V> q, ch;
searched = true;
if (((ch = p.left) != null &&
(q = ch.findTreeNode(h, k, kc)) != null) ||
((ch = p.right) != null &&
(q = ch.findTreeNode(h, k, kc)) != null))
return q;
}
dir = tieBreakOrder(k, pk);
}
TreeNode<K,V> xp = p;
if ((p = (dir <= 0) ? p.left : p.right) == null) {
//当前循环节点xp 即为 x 节点的爸爸
//x 表示插入节点
//f 老的头结点
TreeNode<K,V> x, f = first;
first = x = new TreeNode<K,V>(h, k, v, f, xp);
//条件成立:说明链表有数据
if (f != null)
//设置老的头结点的前置引用为 当前的头结点。
f.prev = x;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
if (!xp.red)
x.red = true;
else {
//表示 当前新插入节点后,新插入节点 与 父节点 形成 “红红相连”
lockRoot();
try {
//平衡红黑树,使其再次符合规范。
root = balanceInsertion(root, x);
} finally {
unlockRoot();
}
}
break;
}
}
assert checkInvariants(root);
return null;
}
private final void lockRoot() {
//条件成立:说明lockState 并不是 0,说明此时有其它读线程在treeBin红黑树中读取数据。
if (!U.compareAndSwapInt(this, LOCKSTATE, 0, WRITER))
contendedLock(); // offload to separate method
}
13.5——TreeBin核心方法find方法源码分析
final Node<K,V> find(int h, Object k) {
if (k != null) {
//e 表示循环迭代的当前节点 迭代的是first引用的链表
for (Node<K,V> e = first; e != null; ) {
//s 保存的是lock临时状态
//ek 链表当前节点 的key
int s; K ek;
//(WAITER|WRITER) => 0010 | 0001 => 0011
//lockState & 0011 != 0 条件成立:说明当前TreeBin 有等待者线程 或者 目前有写操作线程正在加锁
if (((s = lockState) & (WAITER|WRITER)) != 0) {
if (e.hash == h &&
((ek = e.key) == k || (ek != null && k.equals(ek))))
return e;
e = e.next;
}
//前置条件:当前TreeBin中 等待者线程 或者 写线程 都没有
//条件成立:说明添加读锁成功
else if (U.compareAndSwapInt(this, LOCKSTATE, s,
s + READER)) {
TreeNode<K,V> r, p;
try {
//查询操作
p = ((r = root) == null ? null :
r.findTreeNode(h, k, null));
} finally {
//w 表示等待者线程
Thread w;
//U.getAndAddInt(this, LOCKSTATE, -READER) == (READER|WAITER)
//1.当前线程查询红黑树结束,释放当前线程的读锁 就是让 lockstate 值 - 4
//(READER|WAITER) = 0110 => 表示当前只有一个线程在读,且“有一个线程在等待”
//当前读线程为 TreeBin中的最后一个读线程。
//2.(w = waiter) != null 说明有一个写线程在等待读操作全部结束。
if (U.getAndAddInt(this, LOCKSTATE, -READER) ==
(READER|WAITER) && (w = waiter) != null)
//使用unpark 让 写线程 恢复运行状态。
LockSupport.unpark(w);
}
return p;
}
}
}
return null;
}