以下concurrenthashmap以jdk8中為例進行分析,concurrenthashmap是乙個執行緒安全、基於陣列+鍊錶(或者紅黑樹)的kv容器,主要特性如下:
concurrenthashmap預設陣列長度16,map最大容量為maximum_capacity = 1 << 30
。建立concurrenthashmap並不是涉及陣列的初始化,陣列初始化是在第一次put資料才進行的。(注意:jdk1.8中捨棄了之前的分段鎖技術,改用cas+synchronized機制)
concurrenthashmap中乙個重要的類就是node,該類儲存鍵值對,所有插入concurrenthashmap的資料都包裝在這裡面。它與hashmap中的定義很相似,但是有一些差別是concurrenthashmap的value和next屬性都是volatile的(保證了get資料時直接返回即可,volatile保證了更新的可見性
),且不允許呼叫setvalue方法直接改變node的value域,增加了find方法輔助map.get()方法,可在get方法返回的結果中更改對應的value值。
1concurrenthashmap定義了三個原子操作,用於對陣列指定位置的節點進行操作。正是這些原子操作保證了concurrenthashmap的執行緒安全。static
class nodeimplements map.entry
1紅黑樹節點比較既然使用到了紅黑樹,這就涉及到節點的大小比較問題(節點資料報含key、value資訊)。進行節點的大小比較時,首先是比較節點的hash值,注意hash值不是hashcode,因為hash值是物件hashcode與自己無符號右移16位進行異或後的值。如果節點的hash值相等,判斷節點的key物件是否實現了comparable介面,實現的話就是用comparable邏輯比較節點之間的大小。如果key物件未實現comparable介面,則呼叫tiebreakorder方法進行判斷://獲得在i位置上的node節點
2static
final
nodetabat(node tab, int
i) 5//
利用cas演算法設定i位置上的node節點。之所以能實現併發是因為他指定了原來這個節點的值是多少 6//
在cas演算法中,會比較記憶體中的值與你指定的這個值是否相等,如果相等才接受你的修改,否則拒絕你的修改 7//
因此當前執行緒中的值並不是最新的值,這種修改可能會覆蓋掉其他執行緒的修改結果
8static
final
boolean castabat(node tab, int
i,
9 nodec, nodev)
12//
利用volatile方法設定節點位置的值
13static
final
void settabat(node tab, int i, nodev)
1這裡呼叫了system.identityhashcode,將由預設方法hashcode()返回,如果物件的hashcode()被重寫,則system.identityhashcode和hashcode()的返回值就不一樣了。put原始碼//dir = tiebreakorder(k, pk); k/pk,帶比較兩個節點,命名還是挺有意思的
2static
inttiebreakorder(object a, object b)
1get方法比較簡單,給定乙個key來確定value的時候,必須滿足兩個條件hash值相同同時 key相同(equals) ,對於節點可能在鍊錶或樹上的情況,需要分別去查詢。final v putval(k key, v value, boolean
onlyifabsent)
16else
if ((fh = f.hash) ==moved)
17//
在rehash擴容,幫助擴容,擴容完成之後才能繼續進行put操作
18 tab =helptransfer(tab, f);
19else
35 nodepred =e;
36if ((e = e.next) == null
) 41}42
}43else
if (f instanceof treebin) 52}
53}54}
55if (bincount != 0) 62}
63}64//
增加統計值,可能觸發rehash擴容
65 addcount(1l, bincount);
66return
null;67
}6869private
final
void addcount(long x, int
check)
86if (check <= 1)
87return
;88 s =sumcount();89}
90if (check >= 0)
104else
if (u.compareandswapint(this
, sizectl, sc,
105 (rs << resize_stamp_shift) + 2))
106 transfer(tab, null
);107 s =sumcount();
108}
109}
110 }
get時一般是不加鎖(node節點中value資料型別是volatile的,保證了記憶體可見性)。如果slot元素為鍊錶,直接讀取返回即可;如果slot元素為紅黑樹,並且此時該樹在進行再平衡或者節點刪除操作,讀取操作會按照樹節點的next指標進行讀取,也是不加鎖的;如果該樹並沒有進行平衡或者節點刪除操作,那麼會用cas加讀鎖,防止讀取過程中其他執行緒該樹進行更新操作,破壞「讀檢視」。
remove流程就是根據key找到對應節點,將該節點從鍊錶(更改節點前後關係)或者紅黑樹移除的過程,注意,從紅黑樹中刪除元素後,不會將紅黑樹轉換為列表的,只能在put元素時列表可能有轉換紅黑樹操作,不會有反向操作。
注意:hashmap有自動rehash擴容機制,但是當元素remove之後並沒有自動縮容機制,如果陣列經過多次擴容變得很大,並且當前元素較少,請將這些元素轉移到乙個新的hashmap中。
rehash時是成倍擴容(老table和新tablenew),對於table中i位置的所有元素,擴容後會被分配到i和i+table.length這兩個位置中。rehash主要的流程transfer方法中,具體不再展開。
推薦閱讀:
拜託,面試別再問我跳表了!
跳表是乙個隨機化的資料結構,實質就是一種可以進行二分查詢的有序鍊錶。跳表在原有的有序鍊錶上面增加了多級索引,通過索引來實現快速查詢。跳表不僅能提高搜尋效能,同時也可以提高插入和刪除操作的效能。考慮乙個有序鍊錶,我們要查詢3 7 17這幾個元素,我們只能從頭開始遍歷鍊錶,直到查詢到元素為止。上述這個鍊...
拜託,面試別再問我TopK了!!!
除非校招,我在面試過程中從不問topk這個問題,預設大家都知道。將n個數排序之後,取出最大的k個 即為所得。sort arr,1,n return arr 1,k o n lg n 明明只需要topk,卻將全域性都排序了,這也是這個方法複雜度非常高的原因。那能不能不全域性排序,而只區域性排序呢?這就...
拜託,面試別再問我計數排序了!!!
radix sort 還有計數排序 counting sort 今天,1分鐘,通過幾幅圖,爭取讓大家搞懂計數排序。空間大小為o max min 用來儲存所有元素出現次數 計數 掃瞄待排序資料 arr n 使用計數陣列 counting max min 對每乙個 arr n 現的元素進行計數 掃瞄計數...