深入ConcurrrentHashMap三

來源:互聯網
上載者:User

標籤:並發   concurrent   高效能   最佳化   

深入ConcurrentHashMap講得是如何put元素到ConcurrentHashMap中。
這篇主要分析在put元素的時候,需要擴容這時ConcurrentHashMap是如何做的。

先介紹下ConcurrentHashMap的主要思路,步驟如下:
1.在put的時候,如果需要建立HashEntry結點插入到HashEntry中時,這時候如果滿足擴容條件則進入下面第2步
2.擴容調用的是Segment的rehash方法,只對某個segment擴容。這時需要建立一個segment的HashEntry數組,大小為原來2倍大。
3.然後對原有HashEntry數組中的元素進行重hash,ConcurrentHashMap這裡做了最佳化。
 即如果原有的在HashEntry數組中某一個HashEntry鏈,如果裡面的元素,比如有三個元素a->b->c。這裡如果重hash後,a,b,c還是在新數組的相同下標時,可以
想辦法重用這幾個結點。

下面舉例來說明,假設現在一個ConcurrentHashMap,總容量為16,有四個Segment,每個Segment中HashEntry數組大小為4,每個segment的threshold擴容閥值為3。

現在其中一個Segment1的HashEntry儲存情況如下:

這裡可以看到segment1中HashEntry數組已經使用大小為3。其中index0位置的HashEntry因為有hash衝突所以以鏈式來解決衝突,這裡儲存了a,b,c,d,e元素。

index1及index2的hashEntry分別儲存了h1,f1兩個元素。

這裡需要說明的是在做rehash的時候,需要保證盡量不讓reader線程受影響。
rehash的時候會先建立一個大小為原來兩倍的新數組,然後對之前所有元素進行重hash,這個過程中所有的元素重hash後放入新數組位置都是通過建立一個HashEntry來做,以最小化影響之前的reader線程。

這裡假設要put進一個元素到segment1中,key值為p,value為pv。並且這個元素被hash到HashEntry數組的index3位置上,因此需要重建一個HashEntry結點。

這裡會先將當前segment1的count值加1,然後判斷是否該擴容,這裡由於count加1後為4,所以它已經大於上述講到的threshold閥值,因此需要進行擴容。

擴容時會建立一個大小為8的HashEntry數組,然後分別對原有HashEntry數組中的每個元素進行重hash到新數組中。

以分析segment1的index0的HashEntry鏈為例。可以看到有元素: a->b->c->d->e
最簡單粗暴的重hash方法是:
 1.首先對a重hash,這裡假設最終a要儲存到新數組位置index4中,因此要建立一個HashEntry用於儲存key a及相應value。它的next指標指向新數組index4上的首個
HashEntry,這裡為null。
  2.然後對b進行重hash,假設還是放在新數組位置index4中。這時建立一個新HashEntry,並將它的next指標指向index4的首結點,這裡為a。
這步完成後,index4中元素情況如下:
  b->a
 3.依次處理完c,d,e。假設c,d,e都還是在新數組index4中。

有沒有發現a,b,c,d,e,還是被分配在新數組同個位置。因此對它們的建立及重新指向是沒有必要的。
事實上ConcurrentHashMap會有一個指標。
在這個指標指向的結點之前都是認為它們儲存在新數組不同位置,因此對於這部分結點還是要建立HashEntry結點及重新調整next指標。
在這個指標結點之後,則被認為是被分配在新數組相同位置,因此它們這部分鏈條結點可以重用。
我們舉例說明,假設a,b,c結點它們最終放在新數組不同位置,而d和e放在新數組相同位置。
因此對於d和e只需做以下動作:
新數組[i]=d

即這裡直接將新數組下標i的引用指向了元素d。
能這樣做的原因是,rehash後由於擴容後新數組大小為原來2倍。因此對於每個元素在新數組的位置,要麼是在原先位置,要麼是原先位置加上原先大小.
這裡d,e在原先的位置0上,擴容後在新數組的位置4上。之後對於原有數組的下標1,2進行rehash時,它們不可能在被放入到新數組的位置0中。


rehash源碼如下:

private void rehash(HashEntry<K,V> node) {            /*             * Reclassify nodes in each list to new table.  Because we             * are using power-of-two expansion, the elements from             * each bin must either stay at same index, or move with a             * power of two offset. We eliminate unnecessary node             * creation by catching cases where old nodes can be             * reused because their next fields won't change.             * Statistically, at the default threshold, only about             * one-sixth of them need cloning when a table             * doubles. The nodes they replace will be garbage             * collectable as soon as they are no longer referenced by             * any reader thread that may be in the midst of             * concurrently traversing table. Entry accesses use plain             * array indexing because they are followed by volatile             * table write.             */            HashEntry<K,V>[] oldTable = table;            int oldCapacity = oldTable.length;            int newCapacity = oldCapacity << 1;            threshold = (int)(newCapacity * loadFactor);            HashEntry<K,V>[] newTable =                (HashEntry<K,V>[]) new HashEntry[newCapacity];            int sizeMask = newCapacity - 1;            for (int i = 0; i < oldCapacity ; i++) {                HashEntry<K,V> e = oldTable[i];                if (e != null) {                    HashEntry<K,V> next = e.next;                    int idx = e.hash & sizeMask;                    if (next == null)   //  Single node on list                        newTable[idx] = e;                    else { // Reuse consecutive sequence at same slot                        HashEntry<K,V> lastRun = e;                        int lastIdx = idx;                        for (HashEntry<K,V> last = next;                             last != null;                             last = last.next) {                            int k = last.hash & sizeMask;                            if (k != lastIdx) {                                lastIdx = k;                                lastRun = last;                            }                        }                        newTable[lastIdx] = lastRun;                        // Clone remaining nodes                        for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {                            V v = p.value;                            int h = p.hash;                            int k = h & sizeMask;                            HashEntry<K,V> n = newTable[k];                            newTable[k] = new HashEntry<K,V>(h, p.key, v, n);                        }                    }                }            }            int nodeIndex = node.hash & sizeMask; // add the new node            node.setNext(newTable[nodeIndex]);            newTable[nodeIndex] = node;            table = newTable;        }




相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.