此文已由做者趙計剛受權網易雲社區發佈。node
歡迎訪問網易雲社區,瞭解更多網易技術產品運營經驗。數組
四、get(Object key)安全
使用方法:服務器
map.get("hello");
源代碼:數據結構
ConcurrentHashMap的get(Object key)併發
/** * 根據key獲取value * 步驟: * 1)根據key獲取hash值 * 2)根據hash值找到相應的Segment * 調用Segment的get(Object key, int hash) * 3)根據hash值找出HashEntry數組中的索引index,並返回HashEntry[index] * 4)遍歷整個HashEntry[index]鏈表,找出hash和key與給定參數相等的HashEntry,例如e, * 4.1)如沒找到e,返回null * 4.2)如找到e,獲取e.value * 4.2.1)若是e.value!=null,直接返回 * 4.2.2)若是e.value==null,則先加鎖,等併發的put操做將value設置成功後,再返回value值 */ public V get(Object key) { int hash = hash(key.hashCode()); return segmentFor(hash).get(key, hash); }
Segment的get(Object key, int hash)post
/** * 根據key和hash值獲取value */ V get(Object key, int hash) { if (count != 0) { // read-volatile HashEntry<K, V> e = getFirst(hash);//找到HashEntry[index] while (e != null) {//遍歷整個鏈表 if (e.hash == hash && key.equals(e.key)) { V v = e.value; if (v != null) return v; /* * 若是V等於null,有多是當下的這個HashEntry剛剛被建立,value屬性尚未設置成功, * 這時候咱們讀到是該HashEntry的value的默認值null,因此這裏加鎖,等待put結束後,返回value值 */ return readValueUnderLock(e); } e = e.next; } } return null; }
Segment的getFirst(int hash)this
/** * 根據hash值找出HashEntry數組中的索引index,並返回HashEntry[index] */ HashEntry<K, V> getFirst(int hash) { HashEntry<K, V>[] tab = table; return tab[hash & (tab.length - 1)]; }
Segment的readValueUnderLock(HashEntry<K, V> e)spa
V readValueUnderLock(HashEntry<K, V> e) { lock(); try { return e.value; } finally { unlock(); } }
注意點:orm
註釋很重要,必定要看
註釋已經寫明瞭詳細流程,這裏說一下大體流程:
如沒找到e,返回null
如找到e,獲取e.value
若是e.value!=null,直接返回
若是e.value==null,則先加鎖,等併發的put操做將value設置成功後,再返回value值
根據key獲取hash值
根據hash值找到相應的Segment
根據hash值找出Segment中的哪個HashEntry[index]
遍歷整個HashEntry[index]鏈表,找出hash和key與給定參數相等的HashEntry,例如e
對於get操做而言,基本沒有鎖,只有當找到了e且e.value等於null,有多是當下的這個HashEntry剛剛被建立,value屬性尚未設置成功,這時候咱們讀到是該HashEntry的value的默認值null,因此這裏加鎖,等待put結束後,返回value值
聽說,上邊這一點尚未發生過
五、remove(Object key)
使用方法:
map.remove("hello");
源代碼:
ConcurrentHashMap的remove(Object key)
/** * 刪除指定key的元素 * 步驟: * 1)根據key獲取hash值 * 2)根據hash值獲取Segment * 調用Segment的remove(Object key, int hash, Object value) * 1)count-1 * 2)獲取將要刪除的元素所在的HashEntry[index] * 3)遍歷鏈表, * 3.1)若沒有hash和key都與指定參數相同的節點e,返回null * 3.2)如有e,刪除指定節點e,並將e以前的節點從新排序後,將排序後的最後一個節點的下一個節點指定爲e的下一個節點 * (很繞,不知道JDK爲何這樣實現) */ public V remove(Object key) { int hash = hash(key.hashCode()); return segmentFor(hash).remove(key, hash, null); }
Segment的remove(Object key, int hash, Object value)
V remove(Object key, int hash, Object value) { lock(); try { int c = count - 1;//key-value對個數-1 HashEntry<K, V>[] tab = table; int index = hash & (tab.length - 1); HashEntry<K, V> first = tab[index];//獲取將要刪除的元素所在的HashEntry[index] HashEntry<K, V> e = first; //從頭節點遍歷到最後,若未找到相關的HashEntry,e==null,不然,有 while (e != null && (e.hash != hash || !key.equals(e.key))) e = e.next; V oldValue = null; if (e != null) {//將要刪除的節點e V v = e.value; if (value == null || value.equals(v)) { oldValue = v; // All entries following removed node can stay // in list, but all preceding ones need to be // cloned. ++modCount; HashEntry<K, V> newFirst = e.next; /* * 從頭結點遍歷到e節點,這裏將e節點刪除了,可是刪除節點e的前邊的節點會倒序 * eg.本來的順序:E3-->E2-->E1-->E0,刪除E1節點後的順序爲:E2-->E3-->E0 * E1前的節點倒序排列了 */ for (HashEntry<K, V> p = first; p != e; p = p.next) newFirst = new HashEntry<K, V>(p.key, p.hash, newFirst, p.value); tab[index] = newFirst; count = c; // write-volatile } } return oldValue; } finally { unlock(); } }
注意:具體的實現方式看註釋,我的感受比較繞,因此有興趣的朋友能夠按照以下步驟實現了一遍:(實現的過程能夠參照HashMap的remove(Object key))
根據key獲取hash值
根據hash值獲取Segment
獲取將要刪除的元素所在的HashEntry[index]
遍歷鏈表
若沒有hash和key都與指定參數相同的節點e,返回null
如有e,刪除指定節點e,並將e的前一個節點的next指向e的下一個節點,以後count-1
六、containsKey(Object key)
使用方法:
map.containsKey("hello")
源代碼:
ConcurrentHashMap的containsKey(Object key)
/** * 是否包含指定key的數據 * 步驟: * 1)根據key計算hash值 * 2)根據hash獲取相應的Segment * 調用Segment的containsKey(Object key, int hash) * 3)根據hash值找出HashEntry數組中的索引index,並返回HashEntry[index] * 4)遍歷整個HashEntry[index]鏈表,找出hash和key與給定參數相等的HashEntry,例如e, * 4.1)如找到e,返回true * 4.2)如沒找到e,返回false */ public boolean containsKey(Object key) { int hash = hash(key.hashCode()); return segmentFor(hash).containsKey(key, hash); }
Segment的containsKey(Object key, int hash)
boolean containsKey(Object key, int hash) { if (count != 0) { // read-volatile HashEntry<K, V> e = getFirst(hash); while (e != null) { if (e.hash == hash && key.equals(e.key)) return true; e = e.next; } } return false; }
說明:代碼清晰簡單,流程步驟查看註釋便可
七、keySet().iterator()
使用方法:
Map<String, Object> map = new ConcurrentHashMap<String, Object>(); map.put("hello3", "world2"); map.put("hello2", "world"); for(String key : map.keySet()){ System.out.println(map.get(key)); }
源代碼不寫了。
流程:
遍歷每一個Segment中的HashEntry[],完成全部對象的讀取,不加鎖。
八、size()
源代碼:
/** * 計算map中的key-value對總數 * 步驟: * 1)遍歷全部段,計算總的count值sum,計算總的modCount值 * 2)若是有數據的話(modCount!=0),再遍歷全部段一遍,計算總的count值check,在這期間只要有一個段的modCount發生了變化,就再重複如上動做兩次 * 3)若三次後,還未成功,遍歷全部Segment,分別加鎖(即創建全局鎖),而後計算,最後釋放全部鎖 */ public int size() { final Segment<K, V>[] segments = this.segments; long sum = 0;//總量 long check = 0;//標誌位 int[] mc = new int[segments.length];//存放每一個段的modCount for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) { check = 0; sum = 0;//總的count值 int mcsum = 0;//總的modCount值 for (int i = 0; i < segments.length; ++i) {//遍歷全部段 sum += segments[i].count;//計算總的count值 mcsum += mc[i] = segments[i].modCount;//計算總的modCount值 } if (mcsum != 0) {//有數據的話,再檢查一遍 for (int i = 0; i < segments.length; ++i) { check += segments[i].count;//計算總的count if (mc[i] != segments[i].modCount) {//只要有一個段發生了變化(在遍歷期間發生了增刪變化) check = -1; break;//跳出全部循環 } } } if (check == sum)//成功 break; } if (check != sum) { //以上三次都爲成功的話 sum = 0; //每個段所有加鎖(至關於加了一個全局鎖) for (int i = 0; i < segments.length; ++i) segments[i].lock(); //進行統計 for (int i = 0; i < segments.length; ++i) sum += segments[i].count; //所有解鎖 for (int i = 0; i < segments.length; ++i) segments[i].unlock(); } if (sum > Integer.MAX_VALUE) return Integer.MAX_VALUE; else return (int) sum; }
在不加鎖的狀況下遍歷全部Segment,讀取每一個Segment的count和modCount,並進行統計;
完畢後,再遍歷一遍全部Segment,比較modCount,是否發生了變化,若發生了變化,則再重複如上動做兩次;
若三次後,還未成功,遍歷全部Segment,分別加鎖(即創建全局鎖),而後計算,最後釋放全部鎖。
注:以如上的方式,大部分狀況下,不須要加鎖就能夠獲取size()
總結:
數據結構:一個指定個數的Segment數組,數組中的每個元素Segment至關於一個HashTable
加鎖狀況(分段鎖):
put
get中找到了hash與key都與指定參數相同的HashEntry,可是value==null的狀況
remove
size():三次嘗試後,還未成功,遍歷全部Segment,分別加鎖(即創建全局鎖)
免費領取驗證碼、內容安全、短信發送、直播點播體驗包及雲服務器等套餐
更多網易技術、產品、運營經驗分享請點擊。
相關文章:
【推薦】 #3.14 Piday#個人圓周率日