自己啃的JDK8源码。如有错误请指正。如需转载请标明出处。
总结在前
- 首先根据key访问桶要用原子操作。
- 桶为空放新node时用的CAS。
- 桶不空,锁桶,存在key相等替换,不存在则新增。
源码分析
HashMap判断依据是key值。映射到一个hash桶,当key值相等时,替换掉旧值,不相等就追加节点。HashMap的put在上一篇里分析了。那么这次我们直接来看ConcurrentHashMap
,它更改的地方就是HashMap线程不安全的地方。
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
implements ConcurrentMap<K,V>, Serializable {
......
public V put(K key, V value) {
return putVal(key, value, false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
//与HashMap不同,key和value都不能为空
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
//重新for就是没有break,即插入失败
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
//如果key=i这个桶为空,tabAt原子取
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//利用CAS,原子性的放一个对象
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break;//向空桶加元素时没有碰到锁
}
//f当前节点,hash值为-1
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
//桶不空,第一个节点也不是moved,且允许覆盖
else {
V oldVal = null;
//把当前节点锁住
synchronized (f) {
//防止有别的线程抢先锁了table[i]并改变了它,使这个元素存储的地址改变了,这时当前f存的地址就已经不是这个hashtable管理的对象了,要重新for
if (tabAt(tab, i) == f) {
//f的Hash值 >= 0说明合法且是链表
if (fh >= 0) {
binCount = 1;
//binCount和HAshMap不同,这里是桶链表的元素个数
for (Node<K,V> e = f;; ++binCount) {
K ek;
//hash相等且(key的地址相等 或 key的内容相等即重写equals)直接更新
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
//往后一个
Node<K,V> pred = e;
//到最后一个节点了,没找到相等的,新增一个
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
//如果桶里是红黑树不是链表
else if (f instanceof TreeBin) {
Node<K,V> p;
//红黑树第一个节点是root,是没有key的特殊节点,hash值为-2。所以一进去就是第二个节点。
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
//增添完桶里节点数不为0,添成功了
if (binCount != 0) {
//如果大于8,变成红黑树
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
//只有当成功修改才不为空
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
}
谢谢观看,如果有用麻烦点个喜欢,对我是莫大的鼓励。