java8的ConcurrentHashMap爲什麼放棄分段鎖,爲何要使用CAS+Synchronized取代Segment+ReentrantLock

原文地址:https://cloud.tencent.com/developer/article/1509556java

推薦一篇 ConcurrentHashMap 和 HashMap 寫的比較的的文章數組

jdk1.7分段鎖的實現

和hashmap同樣,在jdk1.7中ConcurrentHashMap的底層數據結構是數組加鏈表。和hashmap不一樣的是ConcurrentHashMap中存放的數據是一段段的,即由多個Segment(段)組成的。每一個Segment中都有着相似於數組加鏈表的結構。數據結構

關於Segment

ConcurrentHashMap有3個參數:併發

  1. initialCapacity:初始總容量,默認16
  2. loadFactor:加載因子,默認0.75
  3. concurrencyLevel:併發級別,默認16

其中併發級別控制了Segment的個數,在一個ConcurrentHashMap建立後Segment的個數是不能變的,擴容過程過改變的是每一個Segment的大小。性能

關於分段鎖

段Segment繼承了重入鎖ReentrantLock,有了鎖的功能,每一個鎖控制的是一段,當每一個Segment愈來愈大時,鎖的粒度就變得有些大了。優化

  • 分段鎖的優點在於保證在操做不一樣段 map 的時候能夠併發執行,操做同段 map 的時候,進行鎖的競爭和等待。這相對於直接對整個map同步synchronized是有優點的。
  • 缺點在於分紅不少段時會比較浪費內存空間(不連續,碎片化); 操做map時競爭同一個分段鎖的機率很是小時,分段鎖反而會形成更新等操做的長時間等待; 當某個段很大時,分段鎖的性能會降低。

jdk1.8的map實現

和hashmap同樣,jdk 1.8中ConcurrentHashmap採用的底層數據結構爲數組+鏈表+紅黑樹的形式。數組能夠擴容,鏈表能夠轉化爲紅黑樹。spa

何時擴容?

  1. 當前容量超過閾值
  2. 當鏈表中元素個數超過默認設定(8個),當數組的大小還未超過64的時候,此時進行數組的擴容,若是超過則將鏈表轉化成紅黑樹

何時鏈表轉化爲紅黑樹?

當數組大小已經超過64而且鏈表中的元素個數超過默認設定(8個)時,將鏈表轉化爲紅黑樹.net

ConcurrentHashMap的put操做代碼以下:對象

把數組中的每一個元素當作一個桶。能夠看到大部分都是CAS操做,加鎖的部分是對桶的頭節點進行加鎖,鎖粒度很小。blog

爲何不用ReentrantLock而用synchronized ?

  • 減小內存開銷:若是使用ReentrantLock則須要節點繼承AQS來得到同步支持,增長內存開銷,而1.8中只有頭節點須要進行同步。
  • 內部優化:synchronized則是JVM直接支持的,JVM可以在運行時做出相應的優化措施:鎖粗化、鎖消除、鎖自旋等等。

 

總結:

經過源碼能夠看出 使用 CAS + synchronized 方式時 加鎖的對象是每一個鏈條的頭結點,也就是 鎖定 的是衝突的鏈表,因此再次提升了併發度,併發度等於鏈表的條數或者說 桶的數量。那爲何sement 不把段的大小設置爲一個桶呢,由於在粒度比較小的狀況下,若是使用ReentrantLock則須要節點繼承AQS來得到同步支持,增長內存開銷,而1.8中只有頭節點須要進行同步,粒度表較小,相對來講內存開銷就比較大。因此不把segment的大小設置爲一個桶。

 

參考

原文地址:https://cloud.tencent.com/developer/article/1509556

若有侵權,留言刪除

相關文章
相關標籤/搜索