ConCurrentHashMap的實現原理
一、JDK1.6和JDK1.7中的實現
- 設計思路
ConCurrentHashMap採用分段鎖的思想,每一個段都有一把鎖,從而提高了併發度(即同時操作ConCurrentHashMap而不產生鎖競爭的線程最大數)。另外,當設置併發數時,會默認將併發數改爲2的冪。
所謂的段,就是segment,他和HashMap的結構很像,都在內部有一個Entry數組,並且繼承了重入鎖,用於對段的上鎖。 - 分段創建
JDK1.6會在一開始就創建所有的segment,而JDK1.7除了第一個segment,其餘的segment都是在put時,動態創建的,所以,在每一次查找段的時候,都會判斷段是否爲空,如果爲空則創建。由於ensureSegment方法要在併發場景被調用,所以創建段時,通過CAS算法實現,代碼如下:if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) { // recheck Segment<K,V> s = new Segment<K,V>(lf, threshold, tab); while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) { if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s)) break; } }
- put方法
與JDK1.6不同的是,JDK1.7在put時,會用tryLock(),其餘並無不同。
put時,先通過key找到對應的段,在找到段內的entry,段內操作和HashMap類似。 - get方法和containsKey方法
由於ConCurrentHashMap的get方法和containsKey都是不加鎖的,所以很有可能當ConCurrentHashMap遍歷過程中有其他的線程對其內容進行更改,導致兩方法返回的數據不是最新的數據。 - size方法
size方法也會使用CAS對modcount進行判斷,當多次重試後,會對所有的segment上鎖。 - ConCurrentHashMap不允許key和value爲空
二、JDK1.8的實現
- 設計思路
JDK1.8取消了segment的概念,還是使用的HashMap中數組加鏈表的數據結構,但是對於每一個數組進行加鎖,以調整鎖的粒度。同時,爲了防止hash衝突造成的鏈表過長,當鏈表長度大於8時,會把鏈表變爲紅黑二叉樹的數據結構,當紅黑樹的長度小於等於6時,又可以轉化爲鏈表。 - 使用了synchronized關鍵字而不是重入鎖
可見JDK1.8對synchronized進行了性能優化。