ConcurrentHashMap是一个支持高并发集合,常用的集合之一。
而put()方法是并发里面的重点。
put()方法入口:
public V put(K key, V value) {
return putVal(key, value, false);
}
1.hashMap是允许key和value都为空的;而在ConcurrentHashMap中是不允许的
if (key == null || value == null) throw new NullPointerException();
2.通过spread()方法,可以让高位也能参与到寻址运算
int hash = spread(key.hashCode());
3.binCount的作用是:在桶位中的所属链表的下标位置;
0表示当前桶位为null, node可以直接放着;
2 表示当前桶位已经树化为红黑树
int binCount = 0;
4.进入自旋方法,定义的属性;
// f 表示 桶位的头节点
Node<K,V> f;
// n 表示散列表数组长度
// i 表示key通过寻址计算后,得到的桶位下标
// fn 表示桶位头节点的hash值
int n, i, fh;
5接下来就是四种情况的if判断
CASE1: 条件成立表示当前map中的table尚未初始化;
if (tab == null || (n = tab.length) == 0)
// 需要初始化
tab = initTable();
6.这里又涉及到了initTable()方法,主要是使用CAS原理。
tab:表示map.table的引用
Node<K,V>[] tab;
sc:表示临时局部的sizeCtl值
int sc;
判断table为null,当前散列表尚未初始化;
(tab = table) == null || tab.length == 0
当sizeCtl < 0 ,大概率为-1,其他线程正在进行创建table的过程,当前线程没有竞争到初始化table的锁。
再次判断table为null,防止其他线程已经初始化完毕,然后当前线程再次初始化,导致数据丢失。
(tab = table) == null || tab.length == 0
最终赋值给map.table
table = tab = nt;
并给sc赋值,下次扩容的触发条件
sc = n - (n >>> 2);
下面进入CASE2:
i=(n-1) &hash 表示key使用路由寻址算法得到key 对应table数组的下标位置;
f :tabAt获取指定桶位的头结点
(f = tabAt(tab, i = (n - 1) & hash)) == null
进入到CASE2代码块前置条件:当前table数组i桶位是null时;
使用CAS方式,设置指定数组i桶位为Node,且期望值为null时,
CAS操作成功,直接break
CAS操作失败,表示当前线程之前,有其他线程先进入向指定i桶位设置值了;
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)