高並發下的HashMap,ConcurrentHashMap


參照:

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保證強一致性。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM