Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);
// 自旋cas设置segment,保证线程安全
while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
== null) {
if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
break;
}
}
}
return seg;
}
1、Segment#put真正的操作元素
找到对应的segment
后,就继续对其内部的HashEntry
链表数组进行操作,这个过程中可能会产生哈希冲突,也可能需要扩容,而作者是如何解决实现的呢?
// java.util.concurrent.ConcurrentHashMap.Segment#put
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
// (1)若获取锁,则node=null,没有获取锁,也会做一些事情scanAndLockForPut:
// 拿不到锁,不立即阻塞,而是先自旋,若自旋到一定次数仍未拿到锁,再调用lock()阻塞;
// 在自旋的过程中遍历链表,若发现没有重复的节点,则提前新建一个节点,为后面再插入节省时间。
HashEntry<K,V> node = tryLock() ? null :
scanAndLockForPut(key, hash, value);
V oldValue;
try {
HashEntry<K,V>[] tab = table;
//(2)tab.length是2的整数次方,所以tab.length-1 的二进制就是若干1,对hash 做与运算,算出的index不会超出tab.length-1
int index = (tab.length - 1) & hash;
// 定位到第index个HashEntry
HashEntry<K,V> first = entryAt(tab, index);
for (HashEntry<K,V> e = first;😉 {
if (e != null) {
// (3)e不为空则说明发生hash冲突,解决hash冲突的办法:链表法,新节点作为链表的头节点
// 如果hash值算的不好,经常发生hash冲突,就会造成某一个链表很长,性能就会很低。
K k;
// HashEntry key地址相等 or (hash值相等且key值相等)时
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
oldValue = e.value;
if (!onlyIfAbsent) {
// onlyIfAbsent 为false,则旧值替换为新值,然后break,否则直接break
e.value = value;
++modCount;
}
break;
// put key存在时会新值替换旧值
// putIfAbsent key存在时不替换。
}
// 在e不为null的情况下,向下遍历,直到找到HashEntry的末尾或者 一个key或者hash相等的HashEntry
e = e.next;
}
else {
if (node != null)
// node不为null,说明前面没有抢到锁时,顺便初始化了node
// 同时node放在了HashEntry单列表的头部
node.setNext(first);
else
// 初始化一个node,并放在first链表的头部
node = new HashEntry<K,V>(hash, key, value, first);
int c = count + 1;
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
//(4) 因为count是对一个segment中的HashEntry节点个数的统计,
// 如果hash冲突严重,键值对只添加到HashEntry[]中某几个HashEntry链表中,
// 就造成HashEntry[]有空闲位置,也会造成无味的扩容,内存利用率持续下降
// count 超过了阈值,默认是HashEntry<K,V>[]初始容量*0.75
// 扩容
rehash(node);
else
// (5)更新tab index位置的node
setEntryAt(tab, index, node);
// modCount++
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
unlock();
}
return oldValue;
}
(1)segment
本身是一把锁,且为了确保put元素的过程是线程安全的,必须先尝试获取锁。若tryLock()
获取锁失败,不会立即阻塞,而是执行scanAndLockForPut
,自旋重试一定次数,并且在这个过程中不只是单纯的自旋,还会初始化添加元素需要的节点,为后续获取锁后节省时间,这又是一处体现作者追求极致性能的地方。
/**
- @return a new node if key not found, else null
*/
private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
// 找到对应的HashEntry节点first
HashEntry<K,V> first = entryForHash(this, hash);
HashEntry<K,V> e = first;
HashEntry<K,V> node = null;
int retries = -1; // negative while locating node
while (!tryLock()) {
HashEntry<K,V> f; // to recheck first below
if (retries < 0) {
if (e == null) {
// 若 找到的节点是空的,则进行初始化,为后面拿到锁后,节省时间
if (node == null) // speculatively create node
node = new HashEntry<K,V>(hash, key, value, null);
// retries为0,防止重复初始化
retries = 0;
}
else if (key.equals(e.key))
// 若 找到节点不为空,且 key和准备添加的key相等
retries = 0;