ConcurrentHashMap添加一个键值对有两个方法,一个是put,一个是putIfAbsent,他们底层调用的都是putval方法,只不过put默认第三个参数为false,putIfAbsent为true。
put方法的源码
public V put(K key, V value) {
return putVal(key, value, false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
//如果键值对其中有一个为null,都会抛异常
if (key == null || value == null) throw new NullPointerException();
//获取key的hash值,经过了一定的处理
int hash = spread(key.hashCode());
//声明了一个标识
int binCount = 0;
//table就是键值对数组,这里是对数组做一个死循环
for (Node<K,V>[] tab = table;;) {
//定义相关变量
Node<K,V> f; int n, i, fh;
//第一步判断键值对数组是否为null或者长度为0
if (tab == null || (n = tab.length) == 0)
//如果是的话,初始化键值对数组
tab = initTable();
//如果不是的话,就使用tabAt方法获取键值对数组对应索引位置的Node,赋给变量f
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//如果f为null,就调用casTabAt,其实就是使用的是CAS
//如果键值对数组中tab在索引i处的值为null,那么就把他修改成新加入的节点
//如果插入成功返回true,插入失败返回false
//返回成功则可以退出死循环
//返回失败就继续循环
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break;//退出循环
}
//如果当前要添加的元素和键值对数组上的元素相同时就进行覆盖
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
//出现了hash冲突,需要将数据挂到链表上或者红黑树上
V oldVal = null;
//出现hash冲突采用synchronized
//锁住当前键值对数组中产生hash冲突的位置
synchronized (f) {
//拿到i索引位置的数据,判断跟锁的是不是同一个
if (tabAt(tab, i) == f) {
//fh是当前键值对数组下标i所对应的hash值
if (fh >= 0) {
//用来表示遍历链表节点的个数
binCount = 1;
//每次遍历一个链表的节点就让binCount+1
for (Node<K,V> e = f;; ++binCount) {
K ek;
//值一样,是修改,相当于覆盖
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;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
//如果binCount>=8就要判断是否将链表转成红黑树
if (binCount >= TREEIFY_THRESHOLD)
//尝试转成红黑树
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
spread方法(散列方法):
static final int spread(int h) {
return (h ^ (h >>> 16)) & HASH_BITS;
}
initTable方法:初始化键值对数组
//初始化数组
private final Node<K,V>[] initTable() {
//定义变量
Node<K,V>[] tab; int sc;
//判断数组初始化了没有
while ((tab = table) == null || tab.length == 0) {
//sizeCtl==-1:代表键值对数组正在初始化
//sizeCtl==-n&&<-1:代表正在扩容,扩容线程数位n-1
//sizeCtl==0:还没有初始化
//sizeCtl>0:
//如果没有初始化,就代表要初始化的长度
//如果已经初始化了,代表扩容的阈值
//sizeCtl默认为0
//sizeCtl赋值给sc,并判断是否小于0
if ((sc = sizeCtl) < 0)
//如果小于0,就让一让
Thread.yield();
//sizeCtl大于等于0,满足初始化要求,以CAS的方式将sizeCtl设置为-1
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
//将sizeCtl设置为-1成功之后,就会开始初始化键值对数组
try {
//再次做一次判断
/*
这里需要再做一次判断的原因:
在进入这个逻辑之前,会先判断sc是否大于等于0
如果恰好线程A把这个键值对数组扩容之后,设置了sc的值
此时sc大于等于0,另一个线程B就刚好可以进来,如果这里不再判断的话
线程B会对键值对数组在一次初始化,就会出问题
*/
if ((tab = table) == null || tab.length == 0) {
//如果sc大于0,就设置键值数组长度为sc
//如果不是,就设置键值对数组长度为默认值16
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
//键值对数组初始化完毕
table = tab = nt;
//得到下次扩容的阈值,赋值给sc
sc = n - (n >>> 2);
}
} finally {
//在finally 中将sc赋值给sizeCtl
sizeCtl = sc;
}
break;
}
}
return tab;
}
treeifyBin:尝试转成红黑树
private final void treeifyBin(Node<K,V>[] tab, int index) {
Node<K,V> b; int n, sc;
if (tab != null) {
//数组长度小于64,尝试扩容
if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
//n << 1==n*2
//传入的值是当前键值对数组的两倍
tryPresize(n << 1);
else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
synchronized (b) {
if (tabAt(tab, index) == b) {
TreeNode<K,V> hd = null, tl = null;
for (Node<K,V> e = b; e != null; e = e.next) {
TreeNode<K,V> p =
new TreeNode<K,V>(e.hash, e.key, e.val,
null, null);
if ((p.prev = tl) == null)
hd = p;
else
tl.next = p;
tl = p;
}
setTabAt(tab, index, new TreeBin<K,V>(hd));
}
}
}
}
}
tryPresize
private final void tryPresize(int size) {
//size就是扩容之前数组长度的两倍
//对扩容数组长度做一次判断
//判断是否达到最大值,其次是保证是2的n次幂
int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
//如果传入的长度不是2的n次幂,就会自动转成2的n次幂
tableSizeFor(size + (size >>> 1) + 1);
int sc;
//判断sizeCtl的值是否大于等于0
while ((sc = sizeCtl) >= 0) {
//有两种可能,第一种是没有初始化数组、第二种是已经初始化数组了
Node<K,V>[] tab = table; int n;
//这里是初始化数组的操作
if (tab == null || (n = tab.length) == 0) {
n = (sc > c) ? sc : c;
if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if (table == tab) {
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
}
}
//sc大于等于0,能走到这里说明键值对数组已经初始化了,那么sc就是下一层扩容的阈值
//c是扩容长度,如果扩容长度小于扩容阈值或者大于最大长度,就退出
//相当于有其他线程先行进行扩容
else if (c <= sc || n >= MAXIMUM_CAPACITY)
break;
//开始扩容
else if (tab == table) {
//得到一个扩容戳(32位的数值,高16位作为扩容标识,低16位作为扩容线程数)
//扩容线程数量=低16-1
int rs = resizeStamp(n);
//sc小于0,已经开始扩容了,另一个线程进来会帮助扩容
if (sc < 0) {
Node<K,V>[] nt;
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
transfer(tab, nt);
}
//目前没有线程扩容,当前线程先设置sizeCtl标记,开始扩容
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
//扩容方法
transfer(tab, null);
}
}
}