62,621
社区成员
发帖
与我相关
我的任务
分享final V put(K key, int hash, V value, boolean onlyIfAbsent) {
HashEntry<K,V> node = tryLock() ? null :
scanAndLockForPut(key, hash, value);
V oldValue;
try {
HashEntry<K,V>[] tab = table;
int index = (tab.length - 1) & hash;
HashEntry<K,V> first = entryAt(tab, index);
for (HashEntry<K,V> e = first;;) {
if (e != null) {
K k;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
oldValue = e.value;
if (!onlyIfAbsent) {
e.value = value;
++modCount;
}
break;
}
e = e.next;
}
else {
if (node != null)
node.setNext(first);
else
node = new HashEntry<K,V>(hash, key, value, first);
int c = count + 1;
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
rehash(node);
else
setEntryAt(tab, index, node);
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
unlock();
}
return oldValue;
}
第一点,Segment是ConcurrentHashMap的“分段锁”的实现,每一个Segment都会有一个Lock。可以看到有调用tryLock与unlock方法这也就是“ConcurrentHashMap是有分段锁的论证”。
但是请注意,这里调用锁的方法的命名,是tryLock而不是lock。tryLock调用的是Sync里的tryAcquire方法,这个方法会进行一次cas锁的抢占操作,cas操作是“无锁同步”的核心。在tryLock失败时,会调用scanAndLockForPut方法,下面我们来看看scanAndLockForPut这个方法的实现:
private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
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;
}
else if (key.equals(e.key))
retries = 0;
else
e = e.next;
}
else if (++retries > MAX_SCAN_RETRIES) {
lock();
break;
}
else if ((retries & 1) == 0 &&
(f = entryForHash(this, hash)) != first) {
e = first = f; // re-traverse if entry changed
retries = -1;
}
}
return node;
}
这里维护一个while(!tryLock()),当且仅当retries > MAX_SCAN_RETRIES时才会触发真正的锁定(调用lock方法)。这是“轻量锁转重量锁”的实现。
================
第二点,关于“免锁”,免锁这个词针对的是ConcurrentHashMap的get方法,同样,我们来看看ConcurrentHashMap的get()方法:
public V get(Object key) {
Segment<K,V> s; // manually integrate access methods to reduce overhead
HashEntry<K,V>[] tab;
int h = hash(key.hashCode());
long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
(tab = s.table) != null) {
for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
(tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
e != null; e = e.next) {
K k;
if ((k = e.key) == key || (e.hash == h && key.equals(k)))
return e.value;
}
}
return null;
}
可以看到,在get方法中,做的操作只有两个,第一是获取Segment,第二是获取Segment的HashEntry值,然而,这两个操作都是通过UNSAFE.getObjectVolatile来实现的,UNSAFE.getObjectVolatile这个方法是对象数据获取的原子方法,在应用程序级别没有“锁”,所以,ConcurrentHashMap是“免锁的”。
==================
第三点,关于“副本”,Segment里面维护这一系列的HashEntry,每一个hashEntry本质上就是一个单向链表的头部,也就是说,每一个hashEntry起始都是当前HashEntry加上当前HashEntry的后续Entry的“副本”。这样,对于ConcurrentHashMap来说,每一次获取一个HashEntry就是获取一个“副本”,对这个Entry的操作也就是对“副本的操作”,由于这个副本与原链表是父子关系,所以对这个Entry的更改会自动更新到原链表上。这也就是Bruce Eckel大牛的意思。
第一次码这么多字
,如有疏漏,请指出,欢迎一起讨论~