ConcurrentHashMap源码解读
并发编程须知
CAS(Compare and Swap)
参考CAS技术
Synchornized
实现原理以及对比
ConcurrentHashMap(JDK1.7)
ConcurrentHashMap的添加(JDK1.7)
ConcurrentHashMap获取size(JDK1.7)
ConcurrentHashMap(JDK1.8)
JDK1.8之后,HashMap使用了数组+链表/红黑树的方式存储数据
ConcurrentHashMap作为HashMap的线程安全版本,也不例外。
结构如图:
我们来看下Node类的源码
可以看到基本的属性:
看一下TreeBin的属性
可以看到,里边就是包含着TreeNode(红黑树节点),和一些锁状态的标识
再看一下TreeNode的类结构
可以看到【这是一个红黑树】
static final class TreeNode<K,V> extends Node<K,V> {
TreeNode<K,V> parent; //父节点
TreeNode<K,V> left; //左孩子
TreeNode<K,V> right; //右孩子
TreeNode<K,V> prev;
boolean red; //red是true代表红节点
结论:
基本结构和HashMap(JDK1.8)类似,都是数组+链表/红黑树
不同:
1.使用TreeBin包裹TreeNode,而且其中有一些关于锁的标识位
2.ConcurrentHashMap由于线程安全,操作元素进行了锁的设置
着重来看,ConcurrentHashMap的添加,是怎么保证线程安全的。
ConcurrentHashMap的添加(JDK1.8)
先来贴一段源码
final V putVal(K key, V value, boolean onlyIfAbsent) {
//key是空或者value是空,都抛出异常,说明不允许这么添加。
if (key == null || value == null) throw new NullPointerException();
//开始计算key的哈希码,通过扰动,让散列码更均匀
int hash = spread(key.hashCode());
//用来记录链表的长度的
int binCount = 0;
//开始准备循环
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
//发现数组是空或者数据长度为0,首先初始化~~没有指定初始容量,初始化直接16个
if (tab == null || (n = tab.length) == 0)
tab = initTable();
//(n-1)&hash计算出了key的数组索引位置,发现这个位置的元素是null
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//尝试使用cas替换,比较并替换
//这里需要注意一点,当发现指定的位置是null的时候,cas才会执行
//换句话说cas会尝试把Null替换为==> 新的值
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
//cas成功,就会跳出循环,因为已经添加元素成功了
break; // no lock when adding to empty bin
}
//进到这,说明指定的位置已经有了Node元素,判断该Node的hash码 等不等于 MOVED
//????????????一连串问号
//先不看这个else if,等到看完数据迁移部分的介绍后,再理解这个就很简单了
else if ((fh = f.hash) == MOVED)
// 帮助数据迁移
tab = helpTransfer(tab, f);
//否则说明当前的节点有Node,可能需要拉链,或者做红黑树的插入
else {
V oldVal = null;
//f是链表的头节点,使用同步锁 锁链表的头节点~
synchronized (f) {
if (tabAt(tab, i) == f) {
//hash码 > 0 ,说明这个节点存在,所以需要拉链插入
if (fh >= 0) {
//长度为1
binCount = 1;
//遍历链表
for (Node<K,V> e = f;; ++binCount) {
K ek;
//Key相同,这是覆盖的情况
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) {
//创建新Node,指向最后一个节点就完事了
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
//进到这里,说明不是链表,而是红黑树
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
//将节点,插入到树中
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
//
if (binCount != 0) {
//链表得长度 如果 >= 8
if (binCount >= TREEIFY_THRESHOLD)
//这里注意,这个方法内部还会判断,数组长度是否>64
//不大于64的话,只是做map的扩容
//大于64,转换成红黑树
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
结论
使用CAS+Synhronized插入元素
1.如果没有冲突,使用CAS尝试把插入的元素,放进去
2.CAS成功,插入完成
3.如果CAS失败,则说明可能存在竞争,那么使用Synchronized将链表第一个元素锁住
3.遍历链表,找到链表的尾巴,插入元素