Java常用併發容器總結(二)

張申傲發表於2017-03-31

ConcurrentHashMap

1.介紹

ConcurrentHashMap是一個高效併發的HashMap,它採用了減小鎖粒度的手段,內部進一步細分成了若干個小的HashMap,稱為Segment段。預設情況下,一個ConcurrentHashMap被分為16個段。多ConcurrentHashMap操作時,並不是將整個ConcurrentHashMap加鎖,而是首先根據hashCode定位到要操作的Segment,然後對該段進行加鎖。在多執行緒環境下,如果多個執行緒操作同一個ConcurrentHashMap的不同Segment,可以做到真正的並行,大大提高了效率。

2.程式碼分析

下面以ConcurrentHashMap的put()方法為例,分析ConcurrentHashMap的操作策略:

public V put(K key, V value) {
        Segment<K,V> s;
        if (value == null)
            throw new NullPointerException();

        //計算key的hashCode
        int hash = hash(key);

        //根據hashCode,找到要進行操作的段
        int j = (hash >>> segmentShift) & segmentMask;
        if ((s = (Segment<K,V>)UNSAFE.getObject          
             (segments, (j << SSHIFT) + SBASE)) == null)
            s = ensureSegment(j);

        //對該段進行put
        return s.put(key, hash, value, false);
    }

可以看出,對ConcurrentHashMap的put操作,可以將粒度減小為對某一個Segment的操作,大大減小了鎖的競爭,提高併發效率。
但是,減小鎖的粒度引入了一個新的問題,當系統需要獲得全域性鎖時,消耗的資源較多。以size()方法為例:

public int size() {       
        final Segment<K,V>[] segments = this.segments;
        int size;
        boolean overflow; 
        long sum;        
        long last = 0L;  
        int retries = -1; 
        try {
            for (;;) {
                if (retries++ == RETRIES_BEFORE_LOCK) {
                    for (int j = 0; j < segments.length; ++j)

                        //在這裡,需要多每一個Segment分別加鎖
                        ensureSegment(j).lock(); 
                }
                sum = 0L;
                size = 0;
                overflow = false;
                for (int j = 0; j < segments.length; ++j) {
                    Segment<K,V> seg = segmentAt(segments, j);
                    if (seg != null) {

                        //計算size總數
                        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;
    }

可以看出,在高併發的場景下,ConcurrentHashMap的size()方法的效率要明顯低於HashMap。

3.適用場景

簡單地說,如果想在高併發的場景下使用HashMap,那麼ConcurrentHashMap將是一個很好的選擇。


相關文章