ConcurrentHashMap探究

ConcurrentHashMap

ConcurrentHashMap是線程安全,性能出色的Map的線程安全實現,相比較HashMap他是線程安全的,相比較HashTable他的性能優點很是明顯。他的使用很簡單,這裏主要是想要探究一下ConcurrentHashMap的實現原理。
在這裏一共有 個問題須要搞明白。java

  • ConcurrentHashMap爲何比HashTable的性能要高?
  • ConcurrentHashMap在JDK8和JDK7有什麼變化,爲何會有這種變化,對咱們開發有什麼啓示?
  • 爲何在JDK8中使用Synchronized而不是用ReentrantLock來實現加鎖?

帶着這幾個問題咱們來分析一下ConcurrentHashMap的源碼吧。算法

ConcurrentHashMap定義

在JDK8(JDK7也是同樣)中ConcurrentHashMap的定義以下:數組

public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
    implements ConcurrentMap<K,V>, Serializable {
ConcurrentHashMap在JDK7中的實現

Java7中ConcurrentHashMap的實現是基於分段鎖實現的。他的底層數據結構仍然是數組+鏈表,與HashTable不一樣的是,ConcurrentHashMap的最外層不是一個大的數組,而是一個Segment數組(分段鎖的實現)。分段鎖減少了鎖的粒度,提升了併發程度。這也是爲何比HashTable效率要高的緣由。
HashTable的源碼其實很簡單,HashTable和HashMap的結構一致,可是每個方法都是用Synchronized來修飾,以保證操做是線程安全的。這樣在多線程的狀況下,只有一個線程獲取鎖操做hashTable中的數據。而CourrentHashMap則不是,它容許最多有segment數組長度個線程同時操做ConcurrentHashMap中的數據。安全

ConcurrentHashMap的總體結構以下(圖片來源:http://www.jasongj.com/java/c...):
圖片描述數據結構

ConcurrentHashMap的定義:多線程

public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
        implements ConcurrentMap<K, V>, Serializable {
    private static final long serialVersionUID = 7249069246763182397L;

    /**
     * 表的默認容量
     */
    static final int DEFAULT_INITIAL_CAPACITY = 16;

    /**
     * 默認擴容因子
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * segments數組的默認長度,爲了能經過按位與的散列算法來定位segments數組的索引,必須保證segments數組的長度是2的N次方
     */
    static final int DEFAULT_CONCURRENCY_LEVEL = 16;

    /**
     * HashEntry最大容量
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * segment的最小容量
     */
    static final int MIN_SEGMENT_TABLE_CAPACITY = 2;

    /**
     * segment的最大容量
     */
    static final int MAX_SEGMENTS = 1 << 16; // slightly conservative

    /**
     * 重試次數,無鎖的狀況下嘗試兩次
     */
    static final int RETRIES_BEFORE_LOCK = 2;

    /**
     * 散列運算的掩碼,等於ssize-1
     */
    final int segmentMask;

    /**
     * 定位參與散列運算的位數,等於32-sshift
     */
    final int segmentShift;

    /**
     * 定義segment數組
     */
    final Segment<K,V>[] segments;

Segment定義:併發

static final class Segment<K,V> extends ReentrantLock implements Serializable {
    transient volatile HashEntry<K,V>[] table;
    transient int count;
    transient int modCount;
    //擴容量,默認爲表的容量*加載因子,實際量超過這個值的時候,進行擴容
    transient int threshold;
    //segment中hashEntry的擴容因子
    final float loadFactor;

}
  • get()操做通過兩次散列找到HashEntry,而後進行遍歷的操做,get方法使用的變量都是volatile類型的,能夠保證線程可見,可以被多線程同時讀,而且保證不會讀取到過時的值,在get操做中須要讀count和value兩個共享變量,因此不須要加鎖,volatile字段的寫入操做會先於讀操做,全部即便有一個線程在修改,get也能獲取到最新的值。
  • put() 先對變量的hashCode進行一次再散列而後定位到Segment,而後再Segment中進行插入操做。若是HashEntry數組超過threshold,那麼擴容,擴容只是針對Segment進行擴容。
  • size() 統計ConcurrentHashMap中元素的個數,須要對Segment中全部元素進行求和,Segment裏全局變量count是一個volatile類型的變量,累加能夠獲取元素的總個數,可是不必定準確,由於使用過的count再後面能夠改變,最後的方法就是阻塞put,remove,clean等元素操做的方法,可是這樣很是低效。因此Concurrenthashmap經過嘗試兩次不鎖來統計segment的元素大小,若是兩次結果不同,那麼使用加鎖的方式來統計,容器是否變化是經過modCount是否變化來肯定的。
相關文章
相關標籤/搜索