final V putVal(K key, V value, boolean onlyIfAbsent) {
//HashMap的时候没有对键值对是否为null判断
if (key == null || value == null) throw new NullPointerException();
//获取key的hashcode值
int hash = spread(key.hashCode());
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();
//这个跟hashmap代码一样,通过&得到存放数组索引index值,如果为空表示当前索引值为空,
//通过CAS将值放到索引位置,如果失败则自旋直到成功将节点放到数组中
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
//这句代码表示数组正在扩容(MOVED=-1),正在老数组中的元素转移到新的数组中,不能对老数组进行操作了
//当前线程考虑到它一个人也挺辛苦的 我去帮帮他把 这一块拷贝元素的原理是每个线程去领取任务的形式来搬运节点
//从数组后面开始搬,假如长度为32, 第一个线程领取的任务是 16- 32 这一块区域的节点,第二个线程
//是从0-16之间的节点。
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
//表示当前节点上面已经有元素了,说明这个节点数据结构不是链表就是红黑树了为了避免线程安全问题,
//concurrentHashMap采用了分段锁为了程序性能着想,只锁住一个节点,其它的节点别的线程还是可以操作的。
else {
V oldVal = null;
synchronized (f) {
if (tabAt(tab, i) == f) {
//红黑树
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
//判断当前节点的key和链表中的key是否相同,如果相同 覆盖原始键值对的值
//然后跳出自旋 onlyIfAbsent这个值如果为true表示不覆盖原键值对的值
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;
//如果链表不存在这个key,直接找到链表的最后一个节点,将当前节点直接插入到
//链表的末端,然后跳出循环
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;
//判断是否存在改key,存在覆盖原有的值
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)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
//累加数组中的元素
addCount(1L, binCount);
return null;
}
Node(k,v) table 在初始化的时候考虑了线程安全的问题,用了一个SIZECTL(扩容阈值)
变量来控制初始化状态,当某个线程来进行初始化的时候,通过CAS把SIZECTL
设置为-1,当另一线程也来初始化的时候读到SIZECTL
小于0的时候,表示有线程正在进行实例化,就不能去实例化,就让出cpu的控制权,然后自旋。
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
//判断是否有线程正在初始化数组
if ((sc = sizeCtl) < 0)
//让出cpu的控制权,然后自旋
Thread.yield(); // lost initialization race; just spin
//通过CAS修改SIZECTL的值为-1
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
//得到数组的扩容阈值
sc = n - (n >>> 2);
}
} finally {
//将扩容阈值复制给全局的sizeCtl
sizeCtl = sc;
}
break;
}
}
return tab;
}
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
//得到key的hash值
int h = spread(key.hashCode());
//这里跟hashmap获取值一样,都是通过判断hash和equals来判断,因为concurrentHashMap所有的node节点都是用volatile修 饰的,所以读的时候都需要去
//主内存中读取。
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
//遍历红黑树
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
//遍历链表节点
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}