深入ConcurrentHashMap一

來源:互聯網
上載者:User

標籤:並發   concurrent   線程   高效能   map   

ConcurrentHashMap能夠做到比較高效能的並發訪問。
ConcurrentHashMap內部有一個Segment數組,每個Segment有一個lock。
Segment相當於是一個子map,擁有一個HashEntity數組。
這樣可以將並發壓力分攤到多個Segment上。

ConcurrentHashMap組成圖:


這裡的Segment個數必須為2的n次方,為了之後高效計算要存放的key存放在哪個Segment上(用  << 實現)。

Segment內部擁有一個類型為volatile的HashEntry數組,這是為了能夠讓其它線程看到最新的值。

ConcurrentHashMap擁有put,get,remove等操作。而原有的擴容操作,為了效能則只針對單個Segment來進行。


關鍵的Segment相當了ConcurrentHashMap中的子map,它包含了一個HashEntry數組。它的類圖如下:

這裡關鍵的Segment類圖如下:



HashEntry內部儲存了hash值,final的key值,及volatile的value及next值。

這裡value及next值是volatile是為實現get操作的不需要加鎖提供了基礎。

HashEntry類圖如下:



下面分別從ConcurrentHashMap建立,往ConcurrentHashMap放入元素,從ConcurrentHashMap取出元素來進行分析。

一.ConcurrentHashMap建立

在用ConcurrentHashMap時,可以在建構函式中傳入你想要的map容量,loadFactor裝載因子,並發數(大致決定了有幾個Segment)。
如果不傳入,使用空建構函式時,預設的map容量大小為16,loadFactor為0.75,並發數為16。
我們以預設的參數開始分析。
首先需要確定總容量大小(這裡指所有Segment中存放數組元素的數組大小總和),Segment總數,以及每個Segment中HashEntry數組的大小。
我們先附上代碼:
 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個數ssize取的是以concurrencyLevel為上界的2的n次方,這裡由於concurrencyLevel為16,所以ssize也為16。
然後計算segmentShift,這個值用於後續在存放元素到ConcurrentHashMap時,用於決定有多少位hash高位參與計算存放元素的Segment數組下標。
這裡segmentShift為32-sshift。sshift取的是Segment個數ssize的2的n次數的n值,即在計算ssize時左移的次數,這裡為4。
所以segmentShift為28。
接著計算出segmentMask,這個值用於在計算得到存放元素的Segment所在數組的下標,這裡為了效率使用 & 操作來替代%模操作。
這裡由於Segment個數為16,所以segmentMask為15。
之後會計算出每個Segment的數組容量,這裡先計算出每個Segment的大致大小,即用int c = initialCapacity / ssize; 來得出c,這裡c為1。
之後會初始化一個cap作為真正的segment中HashEntry數組大小,將它初始化為2。
然後為了保證得出的segMent中HashEntry的大小為2的n次方,所以後續會對cap做以下操作:
while (cap < c)
  cap <<= 1;
即cap的值要不是2(c值小於等於2時),或者是大於2的2的n次方。
得到最終每個Segment中HashEntry數組大小後,建立一個Segment數組,並在Segment數組0處初始化建立一個Segment。

最後將ConcurrentHashMap的segments設定為新建立的Segments數組。


這裡可以看到最後將建立的Segment s0是調用UNSAFE.putOrderedObject(ss,SBASE,s0)來將其放入到ss數組中。

這裡的UNSAFE.putOrderedObject是JAVA提供的用於直接操作記憶體的方法,其中參數ss是數組。

UNSAFE.putOrderedObject會延遲更新到記憶體中,但是由於後續在擷取segment數組中的segment時,採用的是UNSAFE.getObjectVolatile,所以能夠保證對於segment放入數組的操作對於後續的線程是可見的。

SBASE是ConcurrentHashMap的一個靜態final long的值,相當於是Segment數組的首地址。

s0則是建立的需要放入ss數組中的segment執行個體。這裡是將s0放入到ss數組位置0中。

如果提供類似:UNSAFE.putOrderedObject(ss,SBASE+offset,s)表明將元素s放入到數組ss的SBASE+offset位置。

其中這裡的offset:

offset=步長*要放在數組第幾位

步長一般是在編譯期已經確定,這裡測試過對於:Double或者Integer等,步長都是4。


相關文章

聯繫我們

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