一張簡易圖
java7 concurrentHashMap使用分段鎖來提高效率,非常有學習意義,
比起全部使用同步方法的hashTable效率要高很多
上圖的結構,外層是一個segment的數組,segment是一個繼承ReentrantLock的類,其內部又有一個
hashEntry的數組,每個hashEntry節點有一個next節點,類似於hashMap的結構。
多線程操作數據時候,外層使用CAS確保數據一致性,內層使用Lock或CAS確保數據一致性。
//put方法過程:
public V put(K key, V value) {
Segment<K,V> s;
if (value == null)
throw new NullPointerException();
int hash = hash(key);
int j = (hash >>> segmentShift) & segmentMask;
if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
s = ensureSegment(j);
//(j << SSHIFT) + SBASE)) 計算在外層數組的位置
//UNSAFE.getObject 使用CAS取得指定位置的值,找到對應的segment
//爲null ensureSegment(j)創建一個
//使用對應的segment的put方法
return s.put(key, hash, value, false);
}
private Segment<K,V> ensureSegment(int k) {
final Segment<K,V>[] ss = this.segments;//volatile類型,數據一致性
long u = (k << SSHIFT) + SBASE; // raw offset
Segment<K,V> seg;
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {//重新檢查
Segment<K,V> proto = ss[0]; // use segment 0 as prototype使用第一個segment的相關設置,初始化方法只
int cap = proto.table.length;//初始了第一個segment
float lf = proto.loadFactor;
int threshold = (int)(cap * lf);
HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
== null) { // recheck
Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);
while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))//使用CAS校驗、設置數據,
== null) { //死循環把new出來的放到指定數組位置
if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
break;
}
}
}
return seg;
}
//segment的put
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;
}
//遍歷鏈表,如果key相等,就更新value,返回
//如果執行到最末尾,都沒有符合的,執行下面的,把新的node的next指向
//first,然後CAS把新的node放在數組索引處,也就是在鏈表頭加上新元素
//中間如果需要擴容,則執行rehash擴容方法
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;
}
private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
HashEntry<K,V> first = entryForHash(this, hash);//CAS找到hashEntry數組指定位置的節點,鏈的第一個
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;
}
//小於0執行的結果是,retries=0
//node == null 新的key在指定索引的鏈上
//node != null new出來的,鏈上沒有或者沒有鏈(即指定索引處還沒有一個元素)
else if (++retries > MAX_SCAN_RETRIES) {//先自增 再比較 大於一個指定值時候
lock();//阻塞獲取鎖,獲取到了則返回
break;
}
else if ((retries & 1) == 0 &&
(f = entryForHash(this, hash)) != first) {//偶數時候檢驗,hashEntry數組
e = first = f; // re-traverse if entry changed 是不是變化了,因爲這個時候並沒有
retries = -1; //獲得鎖,其他線程可能執行了rehash方法,如果改變了重新執行本方法
}
}
return node;
}
//rehash方法
private void rehash(HashEntry<K,V> node) {
/*
* Reclassify nodes in each list to new table. Because we
* are using power-of-two expansion, the elements from
* each bin must either stay at same index, or move with a
* power of two offset. We eliminate unnecessary node
* creation by catching cases where old nodes can be
* reused because their next fields won't change.
* Statistically, at the default threshold, only about
* one-sixth of them need cloning when a table
* doubles. The nodes they replace will be garbage
* collectable as soon as they are no longer referenced by
* any reader thread that may be in the midst of
* concurrently traversing table. Entry accesses use plain
* array indexing because they are followed by volatile
* table write.
*/
HashEntry<K,V>[] oldTable = table;
int oldCapacity = oldTable.length;
int newCapacity = oldCapacity << 1;//擴容兩倍
threshold = (int)(newCapacity * loadFactor);
HashEntry<K,V>[] newTable =
(HashEntry<K,V>[]) new HashEntry[newCapacity];
int sizeMask = newCapacity - 1;
//擴容過程:
//循環舊的所有hashEntry數組的節點,每個節點執行
//如果這個節點不是null,且next==null,那麼這條鏈只有一個,直接複製到新數組
//若next!=null,則先循環一次找到最後一個重新計算後的索引不等於第一個元素的索引的元素
//那麼這個元素後面的元素重新計算索引後都等於這個元素索引,也就是後面的元素重新計算後
//也還在一條鏈上
//然後放置上面找到的元素
//遍歷第一個到上面找到的元素中間的,依次放到新數組中
//其實這個過程,與直接全部循環放置相比,可能提高了一下性能,java8優化了這個過程
for (int i = 0; i < oldCapacity ; i++) {
HashEntry<K,V> e = oldTable[i];
if (e != null) {
HashEntry<K,V> next = e.next;
int idx = e.hash & sizeMask;
if (next == null) // Single node on list
newTable[idx] = e;
else { // Reuse consecutive sequence at same slot
HashEntry<K,V> lastRun = e;
int lastIdx = idx;
for (HashEntry<K,V> last = next;
last != null;
last = last.next) {
int k = last.hash & sizeMask;
if (k != lastIdx) {
lastIdx = k;
lastRun = last;
}
}
newTable[lastIdx] = lastRun;
// Clone remaining nodes
for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {
V v = p.value;
int h = p.hash;
int k = h & sizeMask;
HashEntry<K,V> n = newTable[k];
newTable[k] = new HashEntry<K,V>(h, p.key, v, n);
}
}
}
}
int nodeIndex = node.hash & sizeMask; // add the new node
node.setNext(newTable[nodeIndex]);
newTable[nodeIndex] = node;
table = newTable;
}
//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);
long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
//CAS取得指定索引的segment,然後取出segment的hashEntry數組
//CAS取得計算後hashEntry數組索引處的元素
//鏈表遍歷,比較key,找到則返回
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;
}
還是有些不解的地方,比如scanAndLockForPut爲什麼是偶數次檢驗,scanAndLockForPut得到Node在不在鏈上意義是什麼,
因爲後面的put並沒有使用