ConcurrentHashMap 源碼分析

來源:互聯網
上載者:User

標籤:源碼   concurrenthashmap   

 CocurrentHashMap 作用        HashTable通過對整張表加鎖的方式實現並發hash尋找與儲存,CocurrentHashMapt通過Segment的方式可以實現相同的功能,不過效率更加高,在jdk1.6的時候,CocuentHashMap有弱一致性的問題,不過在jdk1.7的時候,這個問題已經修複了。所以是並發安全性還是效能都是非常高的。接下來我嘗試基於jdk1.7的源碼去分析CocurrentHashMap。
cocurrentHashMap 初始化預先處理
    // Unsafe mechanics    private static final sun.misc.Unsafe UNSAFE;    private static final long SBASE;    private static final int SSHIFT;    private static final long TBASE;    private static final int TSHIFT;    static {        int ss, ts;        try {            UNSAFE = sun.misc.Unsafe.getUnsafe();            Class<?> tc = HashEntry[].class;            Class<?> sc = Segment[].class;            TBASE = UNSAFE.arrayBaseOffset(tc);            SBASE = UNSAFE.arrayBaseOffset(sc);            ts = UNSAFE.arrayIndexScale(tc);            ss = UNSAFE.arrayIndexScale(sc);        } catch (Exception e) {            throw new Error(e);        }        if ((ss & (ss-1)) != 0 || (ts & (ts-1)) != 0)            throw new Error("data type scale not a power of two");        SSHIFT = 31 - Integer.numberOfLeadingZeros(ss);        TSHIFT = 31 - Integer.numberOfLeadingZeros(ts);    }
代碼解析:首先擷取Unsafe提供cas操作,java底層多線程並發都是通過cas完成的,不過cas操作對於高精度的並發還是存在一定問題。【至於這個問題,以後再分析】。UNSAFE.arrayBaseOffset(tc)和UNSAFE.arrayBaseOffset(sc)這兩個都是用於計算HashEntry和Segment實體物件相對於數組對象的記憶體位移值。這是cas操作必須要擷取的值。注釋:
//擷取數組中第一個元素的位移量(get offset of a first element in the array)  public native int arrayBaseOffset(java.lang.Class aClass);  //擷取數組中一個元素的大小(get size of an element in the array)  public native int arrayIndexScale(java.lang.Class aClass); 


cocurrentHashMap 初始化
    public ConcurrentHashMap(int initialCapacity,                             float loadFactor, int concurrencyLevel) {        if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)            throw new IllegalArgumentException();        if (concurrencyLevel > MAX_SEGMENTS)            concurrencyLevel = MAX_SEGMENTS;        // Find power-of-two sizes best matching arguments        int sshift = 0;        int ssize = 1;        while (ssize < concurrencyLevel) {            ++sshift;            ssize <<= 1;        }        this.segmentShift = 32 - sshift;        this.segmentMask = ssize - 1;        if (initialCapacity > MAXIMUM_CAPACITY)            initialCapacity = MAXIMUM_CAPACITY;        int c = initialCapacity / ssize;        if (c * ssize < initialCapacity)            ++c;        int cap = MIN_SEGMENT_TABLE_CAPACITY;        while (cap < c)            cap <<= 1;        // create segments and segments[0]        Segment<K,V> s0 =            new Segment<K,V>(loadFactor, (int)(cap * loadFactor),                             (HashEntry<K,V>[])new HashEntry<?,?>[cap]);        Segment<K,V>[] ss = (Segment<K,V>[])new Segment<?,?>[ssize];        UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]        this.segments = ss;    }

代碼解析:以上代碼主要的作用是初始化Segment[]數組對象以及Segment對象。解析來分析最重要的put和get方法。
   put方法
  <span style="font-size:18px;">  public V put(K key, V value) {        Segment<K,V> s;        if (value == null)            throw new NullPointerException();        int hash = hash(key.hashCode());        int j = (hash >>> segmentShift) & segmentMask;        if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck             (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment            s = ensureSegment(j);        return s.put(key, hash, value, false);    }</span>

代碼解析:大體的意思是先計算出key的hash值,然後利用這個hash值得到Segment對象。然後Segment對象執行put方法。這樣就完成了put操作。由於這個過程非常重要,我們肯定想要知道它是如何處理並發以及 內部實現。
ensureSegment
    private Segment<K,V> ensureSegment(int k) {        final Segment<K,V>[] ss = this.segments;        long u = (k << SSHIFT) + SBASE; // raw offset        Segment<K,V> seg;        if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {            Segment<K,V> proto = ss[0]; // use segment 0 as prototype            int cap = proto.table.length;            float lf = proto.loadFactor;            int threshold = (int)(cap * lf);            HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry<?,?>[cap];            if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))                == null) { // recheck                Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);                while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))                       == null) {                    if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))                        break;                }            }        }        return seg;    }

首先計算出位移量,然後利用UnSafe去擷取對象。在這裡有可能大家對這個位移值的擷取有點疑惑,在這裡我也分析一下這個位移量擷取既long u=(k<<SSHIFT)+SBASE; 大家有可能會提出為什麼會這樣計算呢。一般人都會這樣計算位移值:設k為index,size為對象的大小,_offset為第一個元素的位移值,那位移值的大小應該為offset=index*size+offset。沒錯,理論上這麼來說是對的。但是在java記憶體模型中記憶體遵循8位元組對齊。所以在java記憶體模型中你這樣計算是錯誤的。有前面的初始化可以知道:SSHIFT為對象大小對應的位元對應的位元。所以k<<SSHIFT也就實現了8位元組對齊。
segment->put
        final V put(K key, int hash, V value, boolean onlyIfAbsent) {            HashEntry<K,V> node = tryLock() ? null :                scanAndLockForPut(key, hash, value);            V oldValue;            try {                HashEntry<K,V>[] tab = table;                int index = (tab.length - 1) & hash;                HashEntry<K,V> first = entryAt(tab, index);                for (HashEntry<K,V> e = first;;) {                    if (e != null) {                        K k;                        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 {                        if (node != null)                            node.setNext(first);                        else                            node = new HashEntry<K,V>(hash, key, value, first);                        int c = count + 1;                        if (c > threshold && tab.length < MAXIMUM_CAPACITY)                            rehash(node);                        else                            setEntryAt(tab, index, node);                        ++modCount;                        count = c;                        oldValue = null;                        break;                    }                }            } finally {                unlock();            }            return oldValue;        }
以下畫一個流程圖:



  get()
    public V get(Object key) {        Segment<K,V> s; // manually integrate access methods to reduce overhead        HashEntry<K,V>[] tab;        int h = hash(key.hashCode());        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;    }

原理很簡單先定位到segment,然後定位到實體。並且通過getObjectVolatie保證能夠讀到最新的資料。
總結:concurrentHashMap實現涉及到很多多線程的知識和java記憶體模型這方面的知識,如果沒有足夠的能力,介意不要模仿,但是我們可以學習它的思想以及是如何?的。

 

著作權聲明:本文為博主原創文章,未經博主允許不得轉載。

ConcurrentHashMap 源碼分析

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.