劍指Offer(類庫)——HashMap、HashTable、ConcurrentHashMap(下)

HashTable

HashTable是線程安全的,那麼HashTable做了什麼操作才實現了HashMap沒做到的線程安全呢???

寫個簡單的demo:
在這裏插入圖片描述
進入synchronizedMap內部看一下:
在這裏插入圖片描述
聲明瞭一個mutex修飾對象的成員,使用互斥鎖包圍起來保證了內容互斥,串行化訪問以此來保證線程安全。

HashTable也是一樣的和HashMap實現邏輯沒有什麼區別,是用同樣的去加鎖而已:
在這裏插入圖片描述
但是串行化訪問效率就會降低,那麼既要保證線程安全又要保證效率要怎麼辦呢?這個時候ConcurrentHashMap就出現了。

ConcurrentHashMap

如果不用ConcurrentHashMap,那麼要如何去優化HashTable呢???

通過鎖粒度細化,整鎖拆解成多個鎖進行優化

在JDK5中的ConcurrentHashMap確實這麼實現的,通過分段鎖的Segment實現的結構是數組加上鍊表。而Segment可以對應頭結點去存放默認的16把鎖,區別是比起HashMap給每個頭結點配一把鎖,從串行化的HashTable簡單對比效率提升了16倍。
在這裏插入圖片描述
如果7號鎖下的線程去訪問數組的話,其他節點佔有的線程也可以去訪問這些資源不會被阻塞,只有訪問相同的Segment纔會被阻塞。

但是每個bucket都用不同的鎖去管理,性能上來說還是存在一定的缺陷,JDK8中ConcurrentHashMap使用CAS+synchronized使鎖變得更加細粒度化,同時又進行了優化。

下圖是ConcurrentHashMap的圖解:

JDK8中ConcurrentHashMap隨着最新的HashMap,使用了數組+鏈表+紅黑樹這種結構在效率上進行一定的提升。
在這裏插入圖片描述
下面來看一下ConcurrentHashMap的源碼:

從成員變量來看,和HashMap存在相似之處:
在這裏插入圖片描述
最重要的是sizeCtl的成員變量,是JUC下面ConcurrentHashMap十分關鍵的一個成員變量。

是一個大小控制的標識符,哈希表初始化或者擴容的和時候的一個控制位標示量,負數代表正在進行初始化或者擴容操作,-1代表正在進行初始化,其他的負數代表有n-1個線程正在等待擴容,整合和爲0代表還沒有被初始化。

被volatile修飾,是多線程可見的:
在這裏插入圖片描述
ConcurrentHashMap是使用CAS+synchronized做到高效保證線程安全的,來看看put方法:

簡單描述一下:

  1. 使用for循環,存在CAS操作要保證不斷的去請求操作,之後判斷是否爲空,是空的話就去初始化;
  2. 如果不是空,進行哈希尋址找到頭結點的存儲位置;
  3. 如果這個位置不存在,使用CAS操作創建出來,添加失敗break進入下一個循環;
  4. 如果發現這個key已經存在,說明ConcurrentHashMap正在進行remove,remove說明正在進行擴容就協助進行擴容,執行helpTransfer操作。
    在這裏插入圖片描述
    最後一種情況是哈希碰撞:

這個時候會鎖住鏈表或者紅黑二叉樹的頭結點,就是數組元素,操作如下:
在這裏插入圖片描述
此外ConcurrentHashMap還可以構建本地緩存來降低程序的計算量和負責度。。。
在這裏插入圖片描述
總結一下ConcurrentHashMap的put方法的實現邏輯:

  1. 判斷Node[]數組是否進行了初始化,沒有進行初始化操作;
  2. 通過hash定位數組的索引座標是否有Node節點,如果沒有使用CAS進行添加,添加失敗進行下一次循環;
  3. 檢查到內部正在擴容,就幫助一起擴容;
  4. 如果鏈表頭結點是空,用synchronized鎖住頭結點;
    (1)、如果是Node進行鏈表的添加操作;
    (2)、如果是TreeNode進行樹添加操作;
    (3)、如果是ReservationNode則拋出異常,ReservationNode和ConcurrentHashMap本地緩存相關;
  5. 判斷鏈表長度達到8將鏈表轉換成紅黑樹結構。

總結一下ConcurrentHashMap在JDK8和JDK5版本的差異:

總體還是使用的鎖分離的思想就是比Segment更加細緻,首先使用無鎖操作CAS插入頭結點,失敗就循環重試,如果頭結點已經存在就嘗試獲取頭結點的同步鎖再進行操作。

總結一下HashMap、HashTable、ConcurrentHashMap三者的區別:

  1. HashMap線程不安全,數據結構是數組+鏈表+紅黑樹;
  2. HashTable線程安全,數據結構是數組+鏈表;
  3. ConcurrentHashMap線程安全,使用CAS+同步鎖,數據結構是數組+鏈表+紅黑樹;
  4. HashMap的key和value可以爲null,其他兩個不可以。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章