參照:
http://mp.weixin.qq.com/s/dzNq50zBQ4iDrOAhM4a70A
http://mp.weixin.qq.com/s/1yWSfdz0j-PprGkDgOomhQ
JDK1.7 多線程下死循環
源代碼:
/** * Rehashes the contents of this map into a new array with a * larger capacity. This method is called automatically when the * number of keys in this map reaches its threshold. * * If current capacity is MAXIMUM_CAPACITY, this method does not * resize the map, but sets threshold to Integer.MAX_VALUE. * This has the effect of preventing future calls. * * @param newCapacity the new capacity, MUST be a power of two; * must be greater than current capacity unless current * capacity is MAXIMUM_CAPACITY (in which case value * is irrelevant). */ void resize( int newCapacity) { // 當前數組 Entry[] oldTable = table; // 當前數組容量 int oldCapacity = oldTable.length ; // 如果當前數組已經是默認最大容量MAXIMUM_CAPACITY ,則將臨界值改為Integer.MAX_VALUE 返回 if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } // 使用新的容量創建一個新的鏈表數組 Entry[] newTable = new Entry[newCapacity]; // 將當前數組中的元素都移動到新數組中 transfer(newTable); // 將當前數組指向新創建的數組 table = newTable; // 重新計算臨界值 threshold = (int)(newCapacity * loadFactor); } /** * Transfers all entries from current table to newTable. */ void transfer(Entry[] newTable) { // 當前數組 Entry[] src = table; // 新數組長度 int newCapacity = newTable.length ; // 遍歷當前數組的元素,重新計算每個元素所在數組位置 for (int j = 0; j < src. length; j++) { // 取出數組中的鏈表第一個節點 Entry<K,V> e = src[j]; if (e != null) { // 將舊鏈表位置置空 src[j] = null; // 循環鏈表,挨個將每個節點插入到新的數組位置中 do { // 取出鏈表中的當前節點的下一個節點 Entry<K,V> next = e. next; // 重新計算該鏈表在數組中的索引位置 int i = indexFor(e. hash, newCapacity); // 將下一個節點指向newTable[i] e. next = newTable[i]; // 將當前節點放置在newTable[i]位置 newTable[i] = e; // 下一次循環 e = next; } while (e != null); } } }
resize步驟:
1.擴容
創建一個新的Entry空數組,長度是原數組的2倍。
2.ReHash
遍歷原Entry數組,把所有的Entry重新Hash到新數組。為什么要重新Hash呢?因為長度擴大以后,Hash的規則也隨之改變。

ConcuttrntHashMap
改變線程安全的方法:
- HashTable
- Collections.synchronizedMap
性能是個為你,無論是讀操作還是寫操作,都會給整個集合加鎖

利用 ConcurrentHashMap
Segment是什么呢?Segment本身就相當於一個HashMap對象。
同HashMap一樣,Segment包含一個HashEntry數組,數組中的每一個HashEntry既是一個鍵值對,也是一個鏈表的頭節點。

在ConcurrentHashMap集合中有多少個呢?有2的N次方個segment

ConcurrentHashMap優勢就是采用了[鎖分段技術]
每個segment就好比一個自治區,讀寫操作高度自治,segment之間相互不影響
- 不同Segment的寫入是可以並發執行的。
- 同一Segment的寫和讀是可以並發執行的。
- Segment的寫入是需要上鎖的,因此對同一Segment的並發寫入會被阻塞。
Get方法:
1.為輸入的Key做Hash運算,得到hash值。
2.通過hash值,定位到對應的Segment對象
3.再次通過hash值,定位到Segment當中數組的具體位置。
Put方法:
1.為輸入的Key做Hash運算,得到hash值。
2.通過hash值,定位到對應的Segment對象
3.獲取可重入鎖
4.再次通過hash值,定位到Segment當中數組的具體位置。
5.插入或覆蓋HashEntry對象。
6.釋放鎖。
ConcurrentHashMap讀寫都需要二次定位
size的一致性問題?

源代碼:
public int size() { // Try a few times to get accurate count. On failure due to // continuous async changes in table, resort to locking. final Segment<K,V>[] segments = this.segments; int size; boolean overflow; // true if size overflows 32 bits long sum; // sum of modCounts long last = 0L; // previous sum int retries = -1; // first iteration isn't retry try { for (;;) { if (retries++ == RETRIES_BEFORE_LOCK) { for (int j = 0; j < segments.length; ++j) ensureSegment(j).lock(); // force creation } sum = 0L; size = 0; overflow = false; for (int j = 0; j < segments.length; ++j) { Segment<K,V> seg = segmentAt(segments, j); if (seg != null) { sum += seg.modCount; int c = seg.count; if (c < 0 || (size += c) < 0) overflow = true; } } if (sum == last) break; last = sum; } } finally { if (retries > RETRIES_BEFORE_LOCK) { for (int j = 0; j < segments.length; ++j) segmentAt(segments, j).unlock(); } } return overflow ? Integer.MAX_VALUE : size; }
1.遍歷所有的Segment。
2.把Segment的元素數量累加起來。
3.把Segment的修改次數累加起來。
4.判斷所有Segment的總修改次數是否大於上一次的總修改次數。如果大於,說明統計過程中有修改,重新統計,嘗試次數+1;如果不是。說明沒有修改,統計結束。
5.如果嘗試次數超過閾值,則對每一個Segment加鎖,再重新統計。
6.再次判斷所有Segment的總修改次數是否大於上一次的總修改次數。由於已經加鎖,次數一定和上次相等。
7.釋放鎖,統計結束。
為了盡量不鎖住所有Segment,首先樂觀地假設Size過程中不會有修改。當嘗試一定次數,才無奈轉為悲觀鎖,鎖住所有Segment保證強一致性。
