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


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

推薦一篇 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采用的底層數據結構為數組+鏈表+紅黑樹的形式。數組可以擴容,鏈表可以轉化為紅黑樹。

什么時候擴容?

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

什么時候鏈表轉化為紅黑樹?

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

ConcurrentHashMap的put操作代碼如下:

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

為什么不用ReentrantLock而用synchronized ?

  • 減少內存開銷:如果使用ReentrantLock則需要節點繼承AQS來獲得同步支持,增加內存開銷,而1.8中只有頭節點需要進行同步。
  • 內部優化:synchronized則是JVM直接支持的,JVM能夠在運行時作出相應的優化措施:鎖粗化、鎖消除、鎖自旋等等。

 

總結:

通過源碼可以看出 使用 CAS + synchronized 方式時 加鎖的對象是每個鏈條的頭結點,也就是 鎖定 的是沖突的鏈表,所以再次提高了並發度,並發度等於鏈表的條數或者說 桶的數量。那為什么sement 不把段的大小設置為一個桶呢,因為在粒度比較小的情況下,如果使用ReentrantLock則需要節點繼承AQS來獲得同步支持,增加內存開銷,而1.8中只有頭節點需要進行同步,粒度表較小,相對來說內存開銷就比較大。所以不把segment的大小設置為一個桶。

 

參考

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

如有侵權,留言刪除

 

 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM