最近有點想研究下java.util.concurrent 包下的一些類中的實現,在現實中也對這包裏的類應用不少,但都沒怎麼去深入理解,只是聽說裏面的實現在高併發中有不錯的性能。。接下將對裏面的幾個比較常用的類的源碼進行分析。。
ConcurrentHashMap類
研究源碼時,我一般喜歡從實際的應用中去一步步調試分析。。這樣理解起來容易很多。
實際應用:
ConcurrentMap<String, String> map = new ConcurrentHashMap<String, String>();
String oldValue = map.put("zhxing", "value");
String oldValue1 = map.put("zhxing", "value1");
String oldValue2 = map.putIfAbsent("zhxing", "value2");
String value = map.get("zhxing");
System.out.println("oldValue:" + oldValue);
System.out.println("oldValue1:" + oldValue1);
System.out.println("oldValue2:" + oldValue2);
System.out.println("value:" + value);
輸出結果:
oldValue:null
oldValue1:value
oldValue2:value1
value:value1
先從new 方法開始
/**
* Creates a new, empty map with a default initial capacity (16), load
* factor (0.75) and concurrencyLevel(也就是鎖的個數) (16).
*
*/
public ConcurrentHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
}
// 當都是默認的設置參數
public ConcurrentHashMap(int initialCapacity, float loadFactor,
int concurrencyLevel) {
if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
// MAX_SEGMENTS = 1 << 16,鎖的個數有限制
if (concurrencyLevel > MAX_SEGMENTS)
concurrencyLevel = MAX_SEGMENTS;
// Find power-of-two sizes best matching arguments
// 這裏是根據設定的併發數查找最優的併發數
int sshift = 0;
int ssize = 1;
while (ssize < concurrencyLevel) {
++sshift;
ssize <<= 1;// 不斷右移
}
// 到這裏,sshift=4,ssize=16.因爲concurrencyLevel=16=1<<4
segmentShift = 32 - sshift;// =16
segmentMask = ssize - 1;// =3
// 創建了16個分段(Segment),其實每個分段相當於一個帶鎖的map
this.segments = Segment.newArray(ssize);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
// 這裏是計算每個分段存儲的容量
int c = initialCapacity / ssize;// c=16/16=1
if (c * ssize < initialCapacity)// 防止分段的相加的容量小於總容量
++c;
int cap = 1;
// 如果初始容量比cap的容量小,則已雙倍的容量增加
while (cap < c)
cap <<= 1;
// 分別new分段
for (int i = 0; i < this.segments.length; ++i)
this.segments[i] = new Segment<K, V>(cap, loadFactor);
}
這裏提到了一個Segment 這個類,其實這個是總map 的分段,就是爲了實現分段鎖機制。
/**
* Segments are specialized versions of hash tables. This subclasses from
* ReentrantLock opportunistically, just to simplify some locking and avoid
* separate construction. map 的分段實現,擴展了鎖機制
*/
static final class Segment<K, V> extends ReentrantLock implements
Serializable {
//。。。
Segment(int initialCapacity, float lf) {
loadFactor = lf;
// 這個是開始初始化map容器了
setTable(HashEntry.<K, V> newArray(initialCapacity));
}
/**
* Sets table to new HashEntry array. Call only while holding lock or in
* constructor.
*/
void setTable(HashEntry<K, V>[] newTable) {
threshold = (int) (newTable.length * loadFactor);
table = newTable;
}
}
// 這個是實際保存到map的東西了,如果對HashMap源碼有了解的話,是不是覺得很像Hash.Entry,但又沒實現Map.Entry接口,它是用另外個類WriteThroughEntry
// 來實現這個Map.Entry接口的。
static final class HashEntry<K, V> {
final K key;
final int hash;
volatile V value;
final HashEntry<K, V> next;
HashEntry(K key, int hash, HashEntry<K, V> next, V value) {
this.key = key;
this.hash = hash;
this.next = next;
this.value = value;
}
@SuppressWarnings("unchecked")
// 新建數組,保存着map裏的鍵值對
static final <K, V> HashEntry<K, V>[] newArray(int i) {
return new HashEntry[i];
}
get方法實現
//ConcurrentHashMap類
// 在這裏發現,get操作幾乎是不帶鎖的。。效率提高很多
public V get(Object key) {
// key不能爲null 。。
int hash = hash(key); // throws NullPointerException if key null
return segmentFor(hash).get(key, hash);
}
// 這個hash方式不太懂,估計是爲了能均勻分佈吧
static int hash(Object x) {
int h = x.hashCode();
h += ~(h << 9);
h ^= (h >>> 14);
h += (h << 4);
h ^= (h >>> 10);
return h;
}
/**
* Returns the segment that should be used for key with given hash 這個是尋找所在分段
*
* @param hash
* the hash code for the key
* @return the segment
*/
final Segment<K, V> segmentFor(int hash) {
// hash>>>16&3
return segments[(hash >>> segmentShift) & segmentMask];
}
//Segment 類方法
/* Specialized implementations of map methods */
// 獲得值了,和其他map的get的實現其實差不多
V get(Object key, int hash) {
// count 是每個分段的鍵值對個數,而且是volatile,保證在內存中只有一份
if (count != 0) { // read-volatile
// 獲得分段中hash鏈表的第一個值
HashEntry<K, V> e = getFirst(hash);
while (e != null) {
if (e.hash == hash && key.equals(e.key)) {
V v = e.value;
if (v != null)
return v;
// 這個做了一個挺有趣的檢查,如果v==null,而key!=null,的時候會等待鎖中value的值
return readValueUnderLock(e); // recheck
}
e = e.next;
}
}
return null;
}
/**
* Reads value field of an entry under lock. Called if value field ever
* appears to be null. This is possible only if a compiler happens to
* reorder a HashEntry initialization with its table assignment, which
* is legal under memory model but is not known to ever occur.
*/
V readValueUnderLock(HashEntry<K, V> e) {
lock();
try {
return e.value;
} finally {
unlock();
}
}
put 方法
//ConcurrentHashMap類
// 注意的是key 和value 都不能爲空
public V put(K key, V value) {
if (value == null)
throw new NullPointerException();
// 和get方式一樣的hash 方式
int hash = hash(key);
return segmentFor(hash).put(key, hash, value, false);
}
//Segment 類
V put(K key, int hash, V value, boolean onlyIfAbsent) {
// 這裏加鎖了
lock();
try {
int c = count;
// 如果超過限制,就重新分配
if (c++ > threshold) // ensure capacity
rehash();
HashEntry<K, V>[] tab = table;
int index = hash & (tab.length - 1);
HashEntry<K, V> first = tab[index];
HashEntry<K, V> e = first;
// e的值總是在鏈表的最後一個
while (e != null && (e.hash != hash || !key.equals(e.key)))
e = e.next;
V oldValue;
if (e != null) {
oldValue = e.value;
// 這裏就是實現putIfAbsent 的方式
if (!onlyIfAbsent)
e.value = value;
} else {
oldValue = null;
++modCount;
tab[index] = new HashEntry<K, V>(key, hash, first, value);
count = c; // write-volatile
}
return oldValue;
} finally {
unlock();
}
}
// 這中擴容方式應該和其他map 的擴容一樣
void rehash() {
HashEntry<K, V>[] oldTable = table;
int oldCapacity = oldTable.length;
// 如果到了最大容量則不能再擴容了,max=1<<30,這將可能導致的一個後果是map的操作越來越慢
if (oldCapacity >= MAXIMUM_CAPACITY)
return;
/*
* Reclassify nodes in each list to new Map. 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
* traversing table right now.
*/
// 以兩倍的方式增長
HashEntry<K, V>[] newTable = HashEntry.newArray(oldCapacity << 1);
threshold = (int) (newTable.length * loadFactor);
int sizeMask = newTable.length - 1;
// 下面的數據拷貝就沒多少好講的了
for (int i = 0; i < oldCapacity; i++) {
// We need to guarantee that any existing reads of old Map can
// proceed. So we cannot yet null out each bin.
HashEntry<K, V> e = oldTable[i];
if (e != null) {
HashEntry<K, V> next = e.next;
int idx = e.hash & sizeMask;
// Single node on list
if (next == null)
newTable[idx] = e;
else {
// Reuse trailing 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 all remaining nodes
for (HashEntry<K, V> p = e; p != lastRun; p = p.next) {
int k = p.hash & sizeMask;
HashEntry<K, V> n = newTable[k];
newTable[k] = new HashEntry<K, V>(p.key, p.hash, n,
p.value);
}
}
}
}
table = newTable;
}
size 方法
/**
* Returns the number of key-value mappings in this map. If the map contains
* more than <tt>Integer.MAX_VALUE</tt> elements, returns
* <tt>Integer.MAX_VALUE</tt>. javadoc 上也寫明瞭,返回的數值不能超過Int的最大值,超過也返回最大值
* 在下面的分析也可以看出,爲了減少鎖競爭做了一些性能優化,這種的優化方式在很多方法都有使用
*
* @return the number of key-value mappings in this map
*/
public int size() {
final Segment<K, V>[] segments = this.segments;
long sum = 0;
long check = 0;
int[] mc = new int[segments.length];
// Try a few times to get accurate count. On failure due to
// continuous async changes in table, resort to locking.
// 這裏最多試RETRIES_BEFORE_LOCK=2 次的檢查對比
for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) {
check = 0;
sum = 0;// size 總數
int mcsum = 0;// 修改的總次數
// 這裏保存了一份對比值,供下次對比時使用
for (int i = 0; i < segments.length; ++i) {
sum += segments[i].count;
mcsum += mc[i] = segments[i].modCount;
}
// 只有當map初始化的時候纔等於0
if (mcsum != 0) {
// 在此對比上面保存的修改值
for (int i = 0; i < segments.length; ++i) {
check += segments[i].count;
if (mc[i] != segments[i].modCount) {
check = -1; // force retry
break;
}
}
}
// 檢查和第一次保存值一樣則結束循環
if (check == sum)
break;
}
// 當不相等的時候,這裏就只有用鎖來保證正確性了
if (check != sum) { // Resort to locking all segments
sum = 0;
for (int i = 0; i < segments.length; ++i)
segments[i].lock();
for (int i = 0; i < segments.length; ++i)
sum += segments[i].count;
for (int i = 0; i < segments.length; ++i)
segments[i].unlock();
}
// 這裏也可以看出,如果超過int 的最大值值返回int 最大值
if (sum > Integer.MAX_VALUE)
return Integer.MAX_VALUE;
else
return (int) sum;
}
keys 方法
public Enumeration<K> keys() {
//這裏新建了一個內部Iteraotr 類
return new KeyIterator();
}
//這裏主要是繼承了HashIterator 方法,基本的實現都在HashIterator 中
final class KeyIterator extends HashIterator implements Iterator<K>,
Enumeration<K> {
public K next() {
return super.nextEntry().key;
}
public K nextElement() {
return super.nextEntry().key;
}
}
/* ---------------- Iterator Support -------------- */
// 分析代碼發現,這個遍歷過程沒有涉及到鎖,查看Javadoc 後可知該視圖的 iterator 是一個“弱一致”的迭代器。。
abstract class HashIterator {
int nextSegmentIndex;// 下一個分段的index
int nextTableIndex;// 下一個分段的容器的index
HashEntry<K, V>[] currentTable;// 當前容器
HashEntry<K, V> nextEntry;// 下個鍵值對
HashEntry<K, V> lastReturned;// 上次返回的鍵值對
HashIterator() {
nextSegmentIndex = segments.length - 1;
nextTableIndex = -1;
advance();
}
public boolean hasMoreElements() {
return hasNext();
}
// 先變量鍵值對的鏈表,再對table 數組的index 遍歷,最後遍歷分段數組的index。。這樣就可以完整的變量完所有的entry了
final void advance() {
// 先變量鍵值對的鏈表
if (nextEntry != null && (nextEntry = nextEntry.next) != null)
return;
// 對table 數組的index 遍歷
while (nextTableIndex >= 0) {
if ((nextEntry = currentTable[nextTableIndex--]) != null)
return;
}
// 遍歷分段數組的index
while (nextSegmentIndex >= 0) {
Segment<K, V> seg = segments[nextSegmentIndex--];
if (seg.count != 0) {
currentTable = seg.table;
for (int j = currentTable.length - 1; j >= 0; --j) {
if ((nextEntry = currentTable[j]) != null) {
nextTableIndex = j - 1;
return;
}
}
}
}
}
public boolean hasNext() {
return nextEntry != null;
}
HashEntry<K, V> nextEntry() {
if (nextEntry == null)
throw new NoSuchElementException();
// 把上次的entry換成當前的entry
lastReturned = nextEntry;
// 這裏做一些預操作
advance();
return lastReturned;
}
public void remove() {
if (lastReturned == null)
throw new IllegalStateException();
ConcurrentHashMap.this.remove(lastReturned.key);
lastReturned = null;
}
}
keySet/Values/elements 這幾個方法都和keys 方法非常相似 。。就不解釋了。。而entrySet 方法有點特別。。我也有點不是很明白。。
//這裏沒什麼好說的,看下就明白,主要在下面
public Set<Map.Entry<K, V>> entrySet() {
Set<Map.Entry<K, V>> es = entrySet;
return (es != null) ? es : (entrySet = new EntrySet());
}
final class EntrySet extends AbstractSet<Map.Entry<K, V>> {
public Iterator<Map.Entry<K, V>> iterator() {
return new EntryIterator();
}
}
//主要在這裏,新建了一個WriteThroughEntry 這個類
final class EntryIterator extends HashIterator implements
Iterator<Entry<K, V>> {
public Map.Entry<K, V> next() {
HashEntry<K, V> e = super.nextEntry();
return new WriteThroughEntry(e.key, e.value);
}
}
/**
* Custom Entry class used by EntryIterator.next(), that relays setValue
* changes to the underlying map.
* 這個主要是返回一個Entry,但有點不明白的是爲什麼不在HashEntry中實現Map
* .Entry就可以了(HashMap就是這樣的),爲了減少鎖競爭??
*/
final class WriteThroughEntry extends AbstractMap.SimpleEntry<K, V> {
WriteThroughEntry(K k, V v) {
super(k, v);
}
/**
* Set our entry's value and write through to the map. The value to
* return is somewhat arbitrary here. Since a WriteThroughEntry does not
* necessarily track asynchronous changes, the most recent "previous"
* value could be different from what we return (or could even have been
* removed in which case the put will re-establish). We do not and
* cannot guarantee more.
*/
public V setValue(V value) {
if (value == null)
throw new NullPointerException();
V v = super.setValue(value);
ConcurrentHashMap.this.put(getKey(), value);
return v;
}
}
從上面可以看出,ConcurrentHash 也沒什麼特別的,大概的思路就是採用分段鎖機制來實現的,把之前用一個容易EntryTable來裝的轉換成多個Table來裝鍵值對。而方法裏面的也採用了不少爲了減少鎖競爭而做的一些優化。。從ConcurrentHash類裏面可以看出,它裏面實現了一大堆的內部類。。比如Segment/KeyIterator/ValueIterator/EntryIterator等等。。個人覺得有些代碼好像比較難理解。。比如Segment 類繼承ReentrantLock,爲什麼不用組合呢。。還會有上面提到的,HashEntry 爲什麼不像HashMap 的Entry一樣實現Map.Entry接口。。建立這麼多內部類,搞得人頭暈暈的。。。。