get
public V get(Object key) {
// tab 当前集合的table的临时变量
// e 数组中根据key 的hash值算出的索引位置的节点(以下统称 桶节点)
// p 红黑树中找到的节点
// eh 桶节点的hash值;ek 桶节点的key值
Node<K, V>[] tab; Node<K, V> e, p; int n, eh; K ek;
// key 的hash值
int h = spread(key.hashCode());
// 判断集合数组是否为空,桶节点是否为空
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
// 数组不为空,且桶节点不为空,判断key引用是否与桶节点key引用ek是否一致或者equals一致
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
// key相同,返回桶节点
return e.val;
}
// 桶节点是特殊节点:扩容中,数据在迁移;桶节点为占位节点;是红黑树
else if (eh < 0)
// 桶节点时特殊节点时的查询find
return (p = e.find(h, key)) != null ? p.val : null;
// 桶节点下是链表,遍历链表(根据hash和key值比对,找到节点)
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
// 如果没有进入任何分支,说明集合中没有当前key值的节点,返回null
return null;
}
桶节点是扩容时的数据迁移节点-find
static final class ForwardingNode<K, V> extends Node<K, V> {
final Node<K, V>[] nextTable;
// hash 值为MOVED = -1
ForwardingNode(Node<K, V>[] tab) {
super(MOVED, null, null, null);
this.nextTable = tab;
}
// 数据迁移时的find
Node<K, V> find(int h, Object k) {
// k get(key)中的key;h key的hash值;tab 扩容时新数组的临时变量
outer: for (Node<K, V>[] tab = nextTable;;) {
Node<K, V> e; int n;
// e 新数组中根据h算出索引位置的节点的临时引用,n 新数组长度;
// 如果k为空 ,或者新数组为null,或者新数组长度为0;或者索引位置的节点数据为空,直接返回null
if (k == null || tab == null || (n = tab.length) == 0 ||
(e = tabAt(tab, (n - 1) & h)) == null)
return null;
// 上述条件都不满足,开始死循环
for (;;) {
// e的hash,e的key
int eh; K ek;
// 如果数组中的节点数据不为空,且hash值一致,并且key引用一致或者equals一致,说明要找的就是这个节点
if ((eh = e.hash) == h && ((ek = e.key) == k || (ek != null && k.equals(ek))))
return e;
// 再次发现是特殊节点
if (eh < 0) {
// 套娃,发现再次扩容了,在迁移数据中,重新获取新数组,跳出当前循环到外层循环,继续外层下次循环;
if (e instanceof ForwardingNode) {
tab = ((ForwardingNode<K, V>)e).nextTable;
continue outer;
// 不是扩容,数据迁移时,调用对应的finid,继续查找,(占用节点的find会直接返回null)
} else
return e.find(h, k);
}
// 上述情况都不满足,说明索引位置下是个链表,遍历链表,直到尾部
if ((e = e.next) == null)
// 直到链表尾部都没找到,直接返回null
return null;
}
}
}
}
桶节点是占位节点-find
/**
* 在compute和computeIfAbsent中会使用到占位节点 ReservationNode
*/
static final class ReservationNode<K,V> extends Node<K,V> {
ReservationNode() {
super(RESERVED, null, null);
}
// 占位节点的find直接返回null
Node<K,V> find(int h, Object k) {
return null;
}
}
桶节点是红黑树节点-find
TreeBin-find
// 当前treebin的锁状态,默认0,表示线程没有上锁
volatile int lockState;
对锁状态进行运算的值
// 有现成正在写锁
static final int WRITER = 1;
// 有写线程,在等待获取写锁
static final int WAITER = 2;
// 读线程,在红黑树检索时,需要先对lockState + read
static final int READER = 4;
final Node<K, V> find(int h, Object k) {
if (k != null) {
// e:当前临时节点
// 将当前临时节点指向双向链表头节点
for (Node<K, V> e = first; e != null; ) {
int s; K ek;
/**
* s lockState 临时变量 ;
* WAITER 00000010
* |
* WRITER 00000001
* ------------------
* 00000011
* s & 00000011 != 0
* 说明当前有线程等待或持有写锁,从双向链表中查询
*/
if (((s = lockState) & (WAITER | WRITER)) != 0) {
// 判断双向链表头节点是否与key一致
if (e.hash == h && ((ek = e.key) == k || (ek != null && k.equals(ek))))
// 一致返回当前节点
return e;
// 不一致,遍历链表查找key相同的节点
e = e.next;
// 当前没有线程等待或持有写锁,CAS给 lockState +4,加读锁,读取红黑树
} else if (U.compareAndSwapInt(this, LOCKSTATE, s, s + READER)) {
TreeNode<K, V> r, p;
try {
// 基于findTreeNode在红黑树中检索数据
p = ((r = root) == null ? null : r.findTreeNode(h, k, null));
} finally {
// 会对lockState - 4,读线程拿到数据了,释放读锁
// 可以确认,如果-完4,等于WAITER | READ,说明有写线程可能在等待且当前线程为最后一个读线程,
// 判断waiter是否为null
Thread w;
if (U.getAndAddInt(this, LOCKSTATE, -READER) == (READER | WAITER) && (w = waiter) != null)
// 当前线程是最后一个在红黑树中检索的线程,同时有线程在等待持有写锁,唤醒等待的写线程
LockSupport.unpark(w);
}
return p;
}
}
}
return null;
}
findTreeNode
final TreeNode<K, V> findTreeNode(int h, Object k, Class<?> kc) {
// h 查询key的hash值;k 查询key; kc 查询key 的类对象
// 如果查询的key不为空
if (k != null) {
// 调用findTreeNode的节点-统称当前节点
TreeNode<K, V> p = this;
do {
// ph 当前节点的hash值;dir = -1 查询左子树,dir = 1 查询右子树
// q 递归返回节点的接收值
// pl 当前节点的左子树;pr 当前节点的右子树
int ph, dir; K pk; TreeNode<K, V> q;
TreeNode<K, V> pl = p.left, pr = p.right;
// 如果当前节点的hash值 > 查询key的hash值,将当前节点的左子树指向当前节点
if ((ph = p.hash) > h)
p = pl;
// 如果当前节点的hash值 < 查询key的hash值,将当前节点的右子树指向当前节点
else if (ph < h)
p = pr;
// 如果当前节点的hash值 == 查询key的hash值,比较当前节点key与查询key是否一致,一致返回当前节点
else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
return p;
// 当前节点的左子树是空,循环继续查询右子树
else if (pl == null)
p = pr;
// 当前节点的右子树是空,循环继续查询左子树
else if (pr == null)
p = pl;
// 查询key的hash值与当前key的hash值一致,但是key不一致
else if ((kc != null ||
(kc = comparableClassFor(k)) != null) &&
(dir = compareComparables(kc, k, pk)) != 0)
// 进入判断,表示查询key的类对象不为空,当前类对象实现了ComparAble,重写了compareTo方法,
// 用compareTo决定循环查询左子树还是右子树
p = (dir < 0) ? pl : pr;
// 递归查询红黑树
else if ((q = pr.findTreeNode(h, k, kc)) != null)
return q;
// 循环查询左子树
else
p = pl;
// 遍历红黑树,直到当前节点为空
} while (p != null);
}
return null;
}
}
特殊方法
compute
修改ConcurrentHashMap中指定key的value时,一般会选择先get出来,然后再拿到原value值,基于原value值做一些修改,最后再存放到咱们ConcurrentHashMap
整个流程和putVal方法很类似,但是内部涉及到了占位的情况RESERVED
整个compute方法和putVal的区别就是,compute方法的value需要计算,如果key存在,基于oldValue计算出新结果,如果key不存在,直接基于oldValue为null的情况,去计算新的value。
/**
* 使用compute之前原value替换操作和使用compute之后原value替换操作
*/
private static void computeTest() {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap();
map.put("key", 1);
// 修改key对应的value,追加上1
// 之前的操作方式
Integer oldValue = (Integer) map.get("key");
Integer newValue = oldValue + 1;
map.put("key", newValue);
System.out.println(map);
// 现在的操作方式
map.compute("key", (key, computeOldValue) -> {
if (computeOldValue == null) {
computeOldValue = 0;
}
return computeOldValue + 1;
});
System.out.println(map);
}
compute源码解析
// compute 方法
public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
if (key == null || remappingFunction == null)
throw new NullPointerException();
// 计算key的hash
int h = spread(key.hashCode());
V val = null;
int delta = 0;
int binCount = 0;
// 初始化,桶上赋值,链表插入值,红黑树插入值
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// 初始化
if (tab == null || (n = tab.length) == 0)
tab = initTable();
// 桶上赋值
else if ((f = tabAt(tab, i = (n - 1) & h)) == null) {
// 数组指定的索引位置是没有数据,当前数据必然要放到数组上。
// 因为value需要计算得到,计算的时间不可估计,所以这里并没有通过CAS的方式处理并发操作,直接添加临时占用节点,
// 并占用当前临时节点的锁资源。
Node<K,V> r = new ReservationNode<K,V>();
synchronized (r) {
// 以CAS的方式将数据放上去
if (casTabAt(tab, i, null, r)) {
binCount = 1;
Node<K,V> node = null;
try {
// 如果ReservationNode临时Node存放成功,直接开始计算value
if ((val = remappingFunction.apply(key, null)) != null) {
delta = 1;
// 将计算的value和传入的key封装成一个新Node,通过CAS存储到当前数组上
node = new Node<K,V>(h, key, val, null);
}
} finally {
setTabAt(tab, i, node);
}
}
}
if (binCount != 0)
break;
}
else {
// 省略部分代码。主要是针对在链表上的替换、添加,以及在红黑树上的替换、添加
}
}
if (delta != 0)
addCount((long)delta, binCount);
return val;
}
compute BUG
compute的BUG,如果在计算结果的函数中,又涉及到了当前的key,会造成死锁问题。
public static void main(String[] args) {
ConcurrentHashMap<String,Integer> map = new ConcurrentHashMap();
map.compute("key",(k,v) -> {
return map.compute("key",(key,value) -> {
return 1111;
});
});
System.out.println(map);
}
computeIfPresent和computeIfAbsent
computeIfPresent和computeIfAbsent其实就是将compute方法拆开成了两个方法
compute会在key不存在时,正常存放结果,如果key存在,就基于oldValue计算newValue
computeIfPresent:要求key在map中必须存在,需要基于oldValue计算newValue
computeIfAbsent:要求key在map中不能存在,必须为null,才会基于函数得到value存储进去
replace
涉及到类似CAS的操作,需要将ConcurrentHashMap的value从val1改为val2的场景就可以使用replace实现。
replace内部要求key必须存在,替换value值之前,要先比较oldValue,只有oldValue一致时,才会完成替换操作。
// replace方法调用的replaceNode方法, value:newValue, cv:oldValue
final V replaceNode(Object key, V value, Object cv) {
int hash = spread(key.hashCode());
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// 在数组没有初始化时,或者key不存在时,什么都不干。
if (tab == null || (n = tab.length) == 0 ||
(f = tabAt(tab, i = (n - 1) & hash)) == null)
break;
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
boolean validated = false;
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
validated = true;
for (Node<K,V> e = f, pred = null;;) {
K ek;
// 找到key一致的Node了。
if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) {
// 拿到当前节点的原值。
V ev = e.val;
// 拿oldValue和原值做比较,如果一致,
if (cv == null || cv == ev || (ev != null && cv.equals(ev))) {
// 可以开始替换
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;
}
}
else if (f instanceof TreeBin) {
validated = true;
TreeBin<K,V> t = (TreeBin<K,V>)f;
TreeNode<K,V> r, p;
if ((r = t.root) != null &&
(p = r.findTreeNode(hash, key, null)) != null) {
V pv = p.val;
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));
}
}
}
}
}
if (validated) {
if (oldVal != null) {
if (value == null)
addCount(-1L, -1);
return oldVal;
}
break;
}
}
}
return null;
}
marge
merge(key,value,Function<oldValue,value>);
在使用merge时,有三种情况可能发生:
- 如果key不存在,就跟put(key,value);
- 如果key存在,就可以基于Function计算,得到最终结果
- 结果不为null,将key对应的value,替换为Function的结果
- 结果为null,删除当前key
分析merge源码
public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
if (key == null || value == null || remappingFunction == null) throw new NullPointerException();
int h = spread(key.hashCode());
V val = null;
int delta = 0;
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
// key不存在,直接执行正常的添加操作,将value作为值,添加到hashMap
else if ((f = tabAt(tab, i = (n - 1) & h)) == null) {
if (casTabAt(tab, i, null, new Node<K,V>(h, key, value, null))) {
delta = 1;
val = value;
break;
}
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f, pred = null;; ++binCount) {
K ek;
// 判断链表中,有当前的key
if (e.hash == h && ((ek = e.key) == key || (ek != null && key.equals(ek)))) {
// 基于函数,计算value
val = remappingFunction.apply(e.val, value);
// 如果计算的value不为null,正常替换
if (val != null)
e.val = val;
// 计算的value是null,直接让上一个指针指向我的next,绕过当前节点
else {
delta = -1;
Node<K,V> en = e.next;
if (pred != null)
pred.next = en;
else
setTabAt(tab, i, en);
}
break;
}
pred = e;
if ((e = e.next) == null) {
delta = 1;
val = value;
pred.next =
new Node<K,V>(h, key, val, null);
break;
}
}
}
else if (f instanceof TreeBin) {
binCount = 2;
TreeBin<K,V> t = (TreeBin<K,V>)f;
TreeNode<K,V> r = t.root;
TreeNode<K,V> p = (r == null) ? null :
r.findTreeNode(h, key, null);
val = (p == null) ? value :
remappingFunction.apply(p.val, value);
if (val != null) {
if (p != null)
p.val = val;
else {
delta = 1;
t.putTreeVal(h, key, val);
}
}
else if (p != null) {
delta = -1;
if (t.removeTreeNode(p))
setTabAt(tab, i, untreeify(t.first));
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
break;
}
}
}
if (delta != 0)
addCount((long)delta, binCount);
return val;
}