1. 程式人生 > >ConcurrentHashMap詳解

ConcurrentHashMap詳解

目錄

原始碼為jdk1.7

ConcurrentHashMap介紹

     ConcurrentHashMap 是concurrent包下的一個集合類。它是執行緒安全的雜湊表。它是通過“分段鎖”來實現多執行緒下的安全問題。它將雜湊表分成了不同的段內使用了可重入鎖(ReentrantLock ),不同執行緒只在一個段記憶體線上程的競爭。它不會對整個雜湊表加鎖。

public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
        implements ConcurrentMap<K, V>, Serializable {

它繼承了AbstractMap類,實現了ConcurrentMap,Serializable介面

public abstract class AbstractMap<K,V> implements Map<K,V> {

可以看出AbstractMap類繼承了Map介面,也就是說存放著鍵值對,還有實現了Map介面那些方法。

ConcurrentHashMap底層資料結構

ConcurrentHashMap中的分段鎖稱為Segment,它即類似於HashMap的結構。它內部擁有一個HashEntry陣列,陣列中的每個元素又是一個連結串列;同時又是一個ReentrantLock(Segment繼承了ReentrantLock)。

 static final class Segment<K,V> extends ReentrantLock implements Serializable {

Segment繼承了可重入鎖。

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];
	}
    }

HashEntry這個類有key、value、next、hash四個屬性。HashEntry中的value被volatile修飾,這樣在多執行緒讀寫過程中能夠保持它們的可見性。

ConcurrentHashMap部分分析

ConcurrentHashMap具有的屬性

    //預設容量大小
    static final int DEFAULT_INITIAL_CAPACITY = 16;
    //預設載入因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    //預設併發數量(segments陣列的大小)
    static final int DEFAULT_CONCURRENCY_LEVEL = 16;
    //最大容量
    static final int MAXIMUM_CAPACITY = 1 << 30;
    //最大併發數量
    static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
    static final int RETRIES_BEFORE_LOCK = 2;
    final int segmentMask;
    final int segmentShift;
    //segments陣列
    final Segment<K,V>[] segments;
    //迭代器相關
    transient Set<K> keySet;
    transient Set<Map.Entry<K,V>> entrySet;
    transient Collection<V> values;

建構函式 

public ConcurrentHashMap(int initialCapacity,
                             float loadFactor, int concurrencyLevel) {
        //引數的有效性判斷
        if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
            throw new IllegalArgumentException();
        //如果concurrencyLevel大於最大併發量,將其值設為最大併發量。(segments陣列的大小)
        if (concurrencyLevel > MAX_SEGMENTS)
            concurrencyLevel = MAX_SEGMENTS;

        // Find power-of-two sizes best matching arguments
        int sshift = 0;
        int ssize = 1;
        //sshift記錄ssize左移的次數
        while (ssize < concurrencyLevel) {
            ++sshift;
            ssize <<= 1;
        }
        segmentShift = 32 - sshift;
        segmentMask = ssize - 1;
        this.segments = Segment.newArray(ssize);

        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        //c為每個為table陣列分配的容量
        int c = initialCapacity / ssize;
        if (c * ssize < initialCapacity)
            ++c;
        int cap = 1;
        while (cap < c)
            cap <<= 1;

        for (int i = 0; i < this.segments.length; ++i)
            this.segments[i] = new Segment<K,V>(cap, loadFactor);
    }

 concurrencyLevel的作用就是用來計算segments陣列的容量大小。先計算出“大於或等於concurrencyLevel的最小的2的N次方值”,然後將其儲存為“segments的容量大小(ssize)”。

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);
    }

如果value為null,丟擲異常。不允許值為空。通過key得到hash值。將key-value鍵值對新增到Segment片段中。

 final V put(K key, int hash, V value, boolean onlyIfAbsent) {
            //嘗試獲得鎖,如果失敗則通過scanAndLockForPut方法獲得鎖
            HashEntry<K,V> node = tryLock() ? null :
                scanAndLockForPut(key, hash, value);
            V oldValue;
            try {
                HashEntry<K,V>[] tab = table;
            //  根據”hash值“獲取”HashEntry陣列中對應的HashEntry連結串列“
                int index = (tab.length - 1) & hash;
                HashEntry<K,V> first = entryAt(tab, index);
                for (HashEntry<K,V> e = first;;) {
                    if (e != null) {
                        K k;
                        //如果key重複,則覆蓋舊值
                        if ((k = e.key) == key ||
                            (e.hash == hash && key.equals(k))) {
                            oldValue = e.value;
                            if (!onlyIfAbsent) {
                                e.value = value;
                                ++modCount;
                            }
                            break;
                        }
                        e = e.next;
                    }
                    else {
                    //否則建立新的HashEntry節點
                        if (node != null)
                            node.setNext(first);
                        else
                            node = new HashEntry<K,V>(hash, key, value, first);
                        int c = count + 1;
                    //如果容量大於閾值,需要重hash
                        if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                            rehash(node);
                        else
                            setEntryAt(tab, index, node);
                        ++modCount;
                        count = c;
                        oldValue = null;
                        break;
                    }
                }
            } finally {
            //解鎖
                unlock();
            }
            return oldValue;
        }

put()的作用是將key-value鍵值對插入到“當前Segment對應的HashEntry中”,在插入前它會獲取Segment對應的互斥鎖,插入後會釋放鎖。

rehash()的作用是將”Segment的容量“變為”原始的Segment容量的2倍“。 在將原始的資料拷貝到“新的Segment”中後,會將新增加的key-value鍵值對新增到“新的Segment”中。

get方法

獲取key值對應的hash值,在對應的segment裡面獲取 。

public V get(Object key) {
    Segment<K,V> s;
    HashEntry<K,V>[] tab;
    int h = hash(key);
    long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
    if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
        (tab = s.table) != null) {
        for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
                 (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
             e != null; e = e.next) {
            K k;
            if ((k = e.key) == key || (e.hash == h && key.equals(k)))
                return e.value;
        }
    }
    return null;
}

可以看到get方法並沒有加鎖

get(Object key)的作用是返回key在ConcurrentHashMap裡的值。首先根據key計算出hash值,獲取key對應的Segment片段。如果Segment不為null,則在Segment片段的HashEntry連結串列中遍歷這個連結串列找到對應的HashEntry節點。

由於遍歷過程中其他執行緒可能對連結串列結構做了調整,因此get和containsKey返回的可能是過時的資料,這一點是ConcurrentHashMap在弱一致性上的體現。

ConcurrentHashMap與HashMap、HashTable的區別

  1. ConcurrentHashMap 、HashTable不允許值為null,值為null時丟擲異常,HashMap允許值為null
  2. HashTable所有方法都加鎖synchronized,為執行緒安全的。HashMap不保證執行緒安全。ConcurrentHashMap採用了分段鎖,在迭代的過程中,ConcurrentHashMap僅僅鎖定map的某個部分,而Hashtable則會鎖定整個map。
  3. ConcurrentHashMap為弱一致性,在get方法時有可能獲得過時的資料。