ConcurrentHashMap默認初始大小 16,臨界值:12:基數:0.75
1.ConcurrentHashMap是一個線程安全的hashMap。相對hashMap多出如下一些特殊屬性:java
//默認可以同時運行的線程數目 static final int DEFAULT_CONCURRENCY_LEVEL = 16; //最大同時運行的線程數目 static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
2.ConcurrentHashMap的鏈表實例HashEntry:node
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") static final <K,V> HashEntry<K,V>[] newArray(int i) { return new HashEntry[i]; } }
這裏須要注意的是Value,value並非final的,而是一個volatile.
volatile修飾符告訴編譯程序不要對該變量所參與的操做進行某些優化。在兩種特殊的狀況下須要使用volatile修飾符:
第一種狀況涉及到內存映射硬件(memory-mapped hardware,如圖形適配器,這類設備對計算機來講就好象是內存的一部分同樣),
第二種狀況涉及到共享內存(shared memory,即被兩個以上同時運行的程序所使用的內存)。
大多數計算機擁有一系列寄存器,其存取速度比計算機主存更快。好的編譯程序能進行一種被稱爲「冗餘裝入和存儲的刪去」(redundant load and store removal)的優化,即編譯程序會在程序中尋找並刪去這樣兩類代碼:一類是能夠刪去的從內存裝入數據的指令,由於相應的數據已經被存放在寄存器中;另 一種是能夠刪去的將數據存入內存的指令,由於相應的數據在再次被改變以前能夠一直保留在寄存器中。
若是一個指針變量指向普通內存之外的位置,如指向一個外圍設備的內存映射端口,那麼冗餘裝入和存儲的優化對它來講多是有害的。
ConcurrentHashMap不一樣於HashMap中的一點是,concurrentHashMap的put,get,remvoer等方法的實現都是由其內部類Segment實現的,該內部類:c++
static final class Segment<K,V> extends ReentrantLock implements Serializable {.....}
能夠看出,該類實現了重入鎖保證線程安全,使用final修飾保證方法不被篡改。數組
3.ConcurrentHashMap 中的 readValueUnderLock安全
V readValueUnderLock(HashEntry<K,V> e) { lock(); try { return e.value; } finally { unlock(); } }
該代碼是在值爲空的狀況才調用;該方法在鎖定的狀況下獲取值。由該方法的註釋能夠得知,這樣作是爲了防止在編譯器從新定製一個指定的HashEntry實例初始化時,在內存模型中發生意外。app
/** * 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. */
3.ConcurrentHashMa中的put方法:優化
public V put(K key, V value) { if (value == null) throw new NullPointerException(); int hash = hash(key.hashCode()); return segmentFor(hash).put(key, hash, value, false); }
不容許null的鍵
能夠看出,ConcurrentHashMap和HashMap在對待null鍵的狀況下大相徑庭,HashMap專門開闢了一塊空間用於存儲null鍵的狀況,而ConcurrentHashMap則直接拋出空值針異常。
4.ConcurrentHashMa中segment的put方法:this
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; while (e != null && (e.hash != hash || !key.equals(e.key))) e = e.next; V oldValue; if (e != null) { oldValue = e.value; 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(); }
從該方法能夠看出,根據key的hash值,計算到table下標位置以後,獲取該下標位置的Entry鏈表,而後從鏈表第一個位置開始向後遍歷,分別比 對entry的hash值和key的值,若是都相等且entry不爲空,則獲取 該entry,設置該entry的value爲傳入的value,不然日後遍歷直到鏈表中最後一個位置,直到找到相匹配的key和hash;若是e爲空, 則往該index下插入一個新的entry鏈表。
該方法使用了重入鎖用以保證在同步時候線程的安全。
5.ConcurrentHashMa中segment的rehash方法(當前數組容量不夠,進行擴充的操做):spa
void rehash() { HashEntry<K,V>[] oldTable = table; int oldCapacity = oldTable.length; //若是數組的長度大於或等於臨界值,數組再也不進行擴容。 if (oldCapacity >= MAXIMUM_CAPACITY) return; //擴充數組容量爲原來大小的兩倍。 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; //該鏈表不存在後續節點,直接把該鏈表存入新數組,無需其餘操做 if (next == null) newTable[idx] = e; else { // 存在後續節點,使用臨時變量存儲該鏈表,假設當前節點是最後節點。 HashEntry<K,V> lastRun = e; //獲取下標 int lastIdx = idx; //遍歷該鏈表的後續節點 for (HashEntry<K,V> last = next; last != null; last = last.next) { //獲取後一個節點的index int k = last.hash & sizeMask; //若是後一個節點的index值和前一個不相同, //則使用後節點的index覆蓋前一個節點而且設置該節點爲最後節點,依次 //作相同的操做,直到鏈表的最後一個節點。 if (k != lastIdx) { lastIdx = k; lastRun = last; } } //把鏈表最後節點的值傳遞給數組 //該數組下標爲最後獲取到的下標 newTable[lastIdx] = lastRun; // 遍歷老數組下獲得的鏈表的節點值,複製到新的擴容後的數組中。 for (HashEntry<K,V> p = e; p != lastRun; p = p.next) { //計算鏈表在新數組的下標 int k = p.hash & sizeMask; //獲取數組k下標的鏈表值。 HashEntry<K,V> n = newTable[k]; //把獲取到的鏈表做爲須要插入的新的entry的後續節點。 newTable[k] = new HashEntry<K,V>(p.key, p.hash, n, p.value); } } } } //把擴容後的數組返回 table = newTable; }
該方法的描述見代碼註釋
6.ConcurrentHashMap中的remove方法:線程
public V remove(Object key) { int hash = hash(key.hashCode()); return segmentFor(hash).remove(key, hash, null); }
7.ConcurrentHashMa中segment的remove方法:
V remove(Object key, int hash, Object value) { lock(); try { int c = count - 1; HashEntry<K,V>[] tab = table; int index = hash & (tab.length - 1); HashEntry<K,V> first = tab[index]; HashEntry<K,V> e = first; while (e != null && (e.hash != hash || !key.equals(e.key))) e = e.next; V oldValue = null; if (e != null) { V v = e.value; if (value == null || value.equals(v)) { oldValue = v; // All entries following removed node can stay // in list, but all preceding ones need to be // cloned. ++modCount; HashEntry<K,V> newFirst = e.next; for (HashEntry<K,V> p = first; p != e; p = p.next) newFirst = new HashEntry<K,V>(p.key, p.hash, newFirst, p.value); tab[index] = newFirst; count = c; // write-volatile } } return oldValue; } finally { unlock(); } }
相似於put方法,remove方法也使用了重入鎖來保證線程安全;concurrentHashMap的remove方法不一樣於HashMap的 remove方法,在須要刪除元素的index下的entry鏈表沒有後續節點時候;後者的remove方法本身會負責回收刪除元素的內存而且會移動刪除 元素後面的元素來覆蓋刪除元素的位置,concurrentHashMap的remove方法只會回收內存卻不會和HashMap同樣移動元素。