引入:如何優化Hashtable?
通過鎖細粒度化,將整個鎖拆解成多個鎖進行優化
這兒就引入了早期的ConcurrentHashMap
使用的是分段鎖技術,把Bucket分成幾段來存儲,爲每一段數據都配一把鎖(segment)【這樣做的原因是:爲每個Bucket都添加一把鎖的話,資源消耗大,比如1w個Bucket就要有1w個鎖】
當某個程序訪問某個數據段的時候,就會獲得當前數據段(segment)的鎖,操作其子數組。而如果還有程序要訪問,就只能被阻塞,而其他未被訪問到的數據段依舊可以訪問
早期ConcurrentHashMap分配了16個segment,理論上較Hashtable會提高16倍的效率
注:ConcurrentHashMap是由Segment數組結構和HashEntry數組結構組成。
Segment是一個可重入鎖(ReentrantLock),在ConcurrentHashMap裏扮演鎖的角色;HashEntry則用於存儲鍵值對數據。
一個ConcurrentHashMap裏包含一個Segment數組。Segment的結構和HashMap類似,是一種數組和鏈表結構。一個Segment裏包含一個HashEntry數組,每個HashEntry是一個鏈表結構的元素,每個Segment守護着一個HashEntry數組裏的元素。當對HashEntry數組的數據進行修改時,必須首先獲得與它對應的segment鎖。
這兒就引入了現如今的ConcurrentHashMap,通過CAS和synchronized使鎖進一步細化,結構和JDK1.8的HashMap相同
(附:HashMap是在package java.util包下的,但是ConcurrentHashMap是在package java.util.concurrent下的)
concurrent分段如何實現的?具體一點
源碼分析:
裏面有很多參數和HashMap相同
也有獨特的參數,比如這個控制table大小的變量
進入到源碼中,直接看put方法,發現裏面一開始就有個對key、value判空的條件:
也就是說ConcurrentHashMap是不能夠存放空值的,而HashMap則可以。
然後下一行,能夠看到ConcurrentHashMap對hash值的確定:
由於對數組更新是使用CAS來進行更新的,所以需要不斷去做失敗重試,直到成功爲止
如果發生了hash碰撞,ConcurrentHashMap的處理邏輯:
ConcurrentHashMap:put方法的邏輯
1.判斷Node[]數組是否初始化,沒有就進行初始化操作
2.通過hash定位數組的索引座標,是否有Node節點,如果沒有就使用CAS進行添加(鏈表的頭結點),添加失敗則進入下一次循環
3.如果檢查到內部正在擴容,就幫助它一塊擴容
4.如果f!=null,則使用synchronized鎖住f元素(f元素就是鏈表或者紅黑二叉樹的頭元素)
4.1 如果是Node,則執行鏈表的添加方法
4.2 如果是TreeNode,則執行樹添加結構
5.判斷鏈表長度是否已經達到臨界值8(8是默認值,可以調整),當節點數超過這個值,就需要把鏈表轉化爲樹結構
ConcurrentHashMap的總結:比起Segment,鎖拆的更細
1.首先使用無鎖操作CAS插入頭結點,失敗則循環重試
2.若頭結點已經存在,則嘗試獲取頭結點的同步鎖,再進行操作