1. 程式人生 > >ConCurrentHashMap JDK1.7 和 JDK1.8 的區別

ConCurrentHashMap JDK1.7 和 JDK1.8 的區別

轉自:https://www.jianshu.com/p/933289f27270

ConCurrentHashMap 1.8 相比 1.7的話,主要改變為:

  • 去除 Segment + HashEntry + Unsafe 的實現,
    改為 Synchronized + CAS + Node + Unsafe 的實現
    其實 Node 和 HashEntry 的內容一樣,但是HashEntry是一個內部類。
    用 Synchronized + CAS 代替 Segment ,這樣鎖的粒度更小了,並且不是每次都要加鎖了,CAS嘗試失敗了在加鎖。

  • put()方法中 初始化陣列大小時,1.8不用加鎖,因為用了個 sizeCtl

    變數,將這個變數置為-1,就表明table正在初始化。

下面簡單介紹下主要的幾個方法的一些區別:

1. put() 方法

JDK1.7中的實現:

ConCurrentHashMap 和 HashMap 的put()方法實現基本類似,所以主要講一下為了實現併發性,ConCurrentHashMap 1.7 有了什麼改變

  • 需要定位 2 次 (segments[i],segment中的table[i])
    由於引入segment的概念,所以需要

  1. 先通過key的 rehash值的高位segments陣列大小-1 相與得到在 segments中的位置
  2. 然後在通過 key的rehash值table陣列大小-1 相與得到在table中的位置
  • 沒獲取到 segment鎖的執行緒,沒有權力進行put操作,不是像HashTable一樣去掛起等待,而是會去做一下put操作前的準備:

  1. table[i]的位置(你的值要put到哪個桶中)
  2. 通過首節點first遍歷連結串列找有沒有相同key
  3. 在進行1、2的期間還不斷自旋獲取鎖,超過 64次 執行緒掛起!

JDK1.8中的實現:

  • 先拿到根據 rehash值 定位,拿到table[i]的 首節點first,然後:
  1. 如果為 null
    ,通過 CAS 的方式把 value put進去
  2. 如果 非null ,並且 first.hash == -1 ,說明其他執行緒在擴容,參與一起擴容
  3. 如果 非null ,並且 first.hash != -1 ,Synchronized鎖住 first節點,判斷是連結串列還是紅黑樹,遍歷插入。

2. get() 方法

JDK1.7中的實現:

  • 由於變數 value 是由 volatile 修飾的,java記憶體模型中的 happen before 規則保證了 對於 volatile 修飾的變數始終是 寫操作 先於 讀操作 的,並且還有 volatile 的 記憶體可見性 保證修改完的資料可以馬上更新到主存中,所以能保證在併發情況下,讀出來的資料是最新的資料。

  • 如果get()到的是null值才去加鎖。

JDK1.8中的實現:

  • 和 JDK1.7類似

3. resize() 方法

JDK1.7中的實現:

  • 跟HashMap的 resize() 沒太大區別,都是在 put() 元素時去做的擴容,所以在1.7中的實現是獲得了鎖之後,在單執行緒中去做擴容(1.new個2倍陣列 2.遍歷old陣列節點搬去新陣列)。

JDK1.8中的實現:

  • jdk1.8的擴容支援併發遷移節點,從old陣列的尾部開始,如果該桶被其他執行緒處理過了,就建立一個 ForwardingNode 放到該桶的首節點,hash值為-1,其他執行緒判斷hash值為-1後就知道該桶被處理過了。

4. 計算size

JDK1.7中的實現:

  1. 先採用不加鎖的方式,計算兩次,如果兩次結果一樣,說明是正確的,返回。
  2. 如果兩次結果不一樣,則把所有 segment 鎖住,重新計算所有 segment的 Count 的和

JDK1.8中的實現:

由於沒有segment的概念,所以只需要用一個 baseCount 變數來記錄ConcurrentHashMap 當前 節點的個數

  1. 先嚐試通過CAS 修改 baseCount
  2. 如果多執行緒競爭激烈,某些執行緒CAS失敗,那就CAS嘗試將 CELLSBUSY 置1,成功則可以把 baseCount變化的次數 暫存到一個數組 counterCells 裡,後續陣列 counterCells 的值會加到 baseCount 中。
  3. 如果 CELLSBUSY 置1失敗又會反覆進行CAS baseCount 和 CAS counterCells陣列

相關閱讀:談談ConcurrentHashMap1.7和1.8的不同實現