ConcurrentMap的詳解

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同樣移動元素。

相關文章
相關標籤/搜索