1. 各種map線程安全介紹
1.1 HashMap
HashMap是線程不安全的壁却,在并發(fā)環(huán)境下瘤礁,可能會形成環(huán)狀鏈表(擴容時可能造成阳懂,具體原因自行百度google或查看源碼分析),導(dǎo)致get操作時,cpu空轉(zhuǎn)岩调,所以巷燥,在并發(fā)環(huán)境中使用HashMap是非常危險的。
1.2 HashTable
HashTable和HashMap的實現(xiàn)原理幾乎一樣号枕,差別:
- 1.HashTable不允許key和value為null缰揪;
- 2.HashTable是線程安全的。
但是HashTable線程安全的策略實現(xiàn)代價卻太大堕澄,get/put所有相關(guān)操作都是synchronized的邀跃,相當于給整個哈希表加了一把大鎖,多線程訪問時候蛙紫,只要有一個線程訪問或操作該對象拍屑,那其他線程只能阻塞,相當于將所有的操作串行化坑傅。
1.3 JDK1.7的ConcurrentHashMap
- ConcurrentHashMap采用了非常精妙的"分段鎖"策略僵驰,ConcurrentHashMap的主干是個Segment數(shù)組。
- Segment繼承了ReentrantLock唁毒,所以它就是一種可重入鎖(ReentrantLock)蒜茴。在ConcurrentHashMap,一個Segment就是一個子哈希表浆西,Segment里維護了一個HashEntry數(shù)組粉私,并發(fā)環(huán)境下,對于不同Segment的數(shù)據(jù)進行操作是不用考慮鎖競爭的近零。
- 所以诺核,對于同一個Segment的操作才需考慮線程同步,不同的Segment則無需考慮久信。
- 使用ConConcurrentHashMap時候有時候會遇到跨段的問題窖杀,跨段的時候[size()、containsValue()]裙士,可能需要鎖定部分段或者全段入客,當操作結(jié)束之后,又回按照順序進行釋放每一段的鎖腿椎。注意是按照順序解鎖的桌硫。,每個Segment又包含了多個HashEntry.
主要結(jié)構(gòu):ReentrantLock+Segment+HashEntry
1.7的ConcurrentHashMap源碼:
JDK1.8的ConcurrentHashMap
JDK1.8的實現(xiàn)已經(jīng)拋棄了Segment分段鎖機制啃炸,利用CAS+Synchronized來保證并發(fā)更新的安全鞍泉。數(shù)據(jù)結(jié)構(gòu)采用:數(shù)組+鏈表+紅黑樹。
從JDK1.7版本的ReentrantLock+Segment+HashEntry肮帐,到JDK1.8版本中synchronized+CAS+HashEntry+紅黑樹。
2. JDK1.8的ConcurrentHashMap
2.1 put()
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw newNullPointerException(); // 鍵或值為空,拋出異常
// 鍵的hash值經(jīng)過計算獲得hash值
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) { // 無限循環(huán)
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0) //表為空或者表的長度為0
// 初始化表
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash))== null) { //表不為空并且表的長度大于0训枢,并且該桶不為空
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key,value, null))) //比較并且交換值托修,如tab的第i項空則用新生成的node替換
break; // no lockwhen adding to empty bin
}
else if ((fh = f.hash) == MOVED) //該結(jié)點的hash值為MOVED
// 進行結(jié)點的轉(zhuǎn)移(在擴容的過程中)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
synchronized (f) { // 加鎖同步
if (tabAt(tab, i) == f) {//找到table表下標為i的節(jié)點
if (fh >= 0) { // 該table表中該結(jié)點的hash值大于0
// binCount賦值為1
binCount = 1;
for (Node<K,V> e = f;; ++binCount) { // 無限循環(huán)
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) { // 結(jié)點的hash值相等并且key也相等
// 保存該結(jié)點的val值
oldVal = e.val;
if (!onlyIfAbsent) // 進行判斷
// 將指定的value保存至結(jié)點,即進行了結(jié)點值的更新
e.val = value;
break;
}
// 保存當前結(jié)點
Node<K,V> pred = e;
if ((e = e.next) == null){ // 當前結(jié)點的下一個結(jié)點為空恒界,即為最后一個結(jié)點
// 新生一個結(jié)點并且賦值給next域
pred.next = new Node<K,V>(hash, key,
value, null);
// 退出循環(huán)
break;
}
}
}
else if (f instanceof TreeBin) { // 結(jié)點為紅黑樹結(jié)點類型
Node<K,V> p;
// binCount賦值為2
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) { // 將hash睦刃、key、value放入紅黑樹
// 保存結(jié)點的val
oldVal = p.val;
if (!onlyIfAbsent) // 判斷
// 賦值結(jié)點value值
p.val = value;
}
}
}
}
if (binCount != 0) { // binCount不為0
if (binCount >= TREEIFY_THRESHOLD) // 如果binCount大于等于轉(zhuǎn)化為紅黑樹的閾值
// 進行轉(zhuǎn)化
treeifyBin(tab, i);
if (oldVal != null) // 舊值不為空
// 返回舊值
return oldVal;
break;
}
}
}
// 增加binCount的數(shù)量
addCount(1L, binCount);
return null;
}
說明:put函數(shù)底層調(diào)用了putVal進行數(shù)據(jù)的插入十酣,對于putVal函數(shù)的流程大體如下涩拙。
判斷存儲的key、value是否為空耸采,若為空兴泥,則拋出異常,否則虾宇,進入步驟②
計算key的hash值搓彻,隨后進入無限循環(huán),該無限循環(huán)可以確保成功插入數(shù)據(jù)嘱朽,若table表為空或者長度為0旭贬,則初始化table表,否則搪泳,進入步驟③
根據(jù)key的hash值取出table表中的結(jié)點元素稀轨,若取出的結(jié)點為空(該桶為空),則使用CAS將key岸军、value奋刽、hash值生成的結(jié)點放入桶中。否則凛膏,進入步驟④
若該結(jié)點的的hash值為MOVED杨名,則對該桶中的結(jié)點進行轉(zhuǎn)移,否則猖毫,進入步驟⑤
對桶中的第一個結(jié)點(即table表中的結(jié)點)進行加鎖台谍,對該桶進行遍歷,桶中的結(jié)點的hash值與key值與給定的hash值和key值相等吁断,則根據(jù)標識選擇是否進行更新操作(用給定的value值替換該結(jié)點的value值)趁蕊,若遍歷完桶仍沒有找到hash值與key值和指定的hash值與key值相等的結(jié)點,則直接新生一個結(jié)點并賦值為之前最后一個結(jié)點的下一個結(jié)點仔役。進入步驟⑥
若binCount值達到紅黑樹轉(zhuǎn)化的閾值掷伙,則將桶中的結(jié)構(gòu)轉(zhuǎn)化為紅黑樹存儲,最后又兵,增加binCount的值任柜。
在putVal函數(shù)中會涉及到如下幾個函數(shù):initTable卒废、tabAt、casTabAt宙地、helpTransfer摔认、putTreeVal、treeifyBin宅粥、addCount函數(shù)参袱。下面對其中涉及到的函數(shù)進行分析。
2.2 initTable()
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) { // 無限循環(huán)
if ((sc = sizeCtl) < 0) // sizeCtl小于0秽梅,則進行線程讓步等待
Thread.yield(); // lost initialization race; just spin
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { // 比較sizeCtl的值與sc是否相等抹蚀,相等則用-1替換
try {
if ((tab = table) == null || tab.length == 0) { // table表為空或者大小為0
// sc的值是否大于0,若是企垦,則n為sc环壤,否則,n為默認初始容量
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
// 新生結(jié)點數(shù)組
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
// 賦值給table
table = tab = nt;
// sc為n * 3/4
sc = n - (n >>> 2);
}
} finally {
// 設(shè)置sizeCtl的值
sizeCtl = sc;
}
break;
}
}
// 返回table表
return tab;
}
說明:對于table的大小竹观,會根據(jù)sizeCtl的值進行設(shè)置镐捧,如果沒有設(shè)置szieCtl的值,那么默認生成的table大小為16臭增,否則懂酱,會根據(jù)sizeCtl的大小設(shè)置table大小。
2.3 helpTransfer()
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
Node<K,V>[] nextTab; int sc;
if (tab != null && (f instanceof ForwardingNode) &&
(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) { // table表不為空并且結(jié)點類型使ForwardingNode類型誊抛,并且結(jié)點的nextTable不為空
int rs = resizeStamp(tab.length);
while (nextTab == nextTable && table == tab &&
(sc = sizeCtl) < 0) { // 條件判斷
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || transferIndex <= 0) //
break;
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { // 比較并交換
// 將table的結(jié)點轉(zhuǎn)移到nextTab中
transfer(tab, nextTab);
break;
}
}
return nextTab;
}
return table;
}
此函數(shù)用于在擴容時將table表中的結(jié)點轉(zhuǎn)移到nextTable中列牺。
2.4 putTreeVal()
final TreeNode<K,V> putTreeVal(int h, K k, V v) {
Class<?> kc = null;
boolean searched = false;
for (TreeNode<K,V> p = root;;) {
int dir, ph; K pk;
if (p == null) {
first = root = new TreeNode<K,V>(h, k, v, null, null);
break;
}
else if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
return p;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0) {
if (!searched) {
TreeNode<K,V> q, ch;
searched = true;
if (((ch = p.left) != null &&
(q = ch.findTreeNode(h, k, kc)) != null) ||
((ch = p.right) != null &&
(q = ch.findTreeNode(h, k, kc)) != null))
return q;
}
dir = tieBreakOrder(k, pk);
}
TreeNode<K,V> xp = p;
if ((p = (dir <= 0) ? p.left : p.right) == null) {
TreeNode<K,V> x, f = first;
first = x = new TreeNode<K,V>(h, k, v, f, xp);
if (f != null)
f.prev = x;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
if (!xp.red)
x.red = true;
else {
lockRoot();
try {
root = balanceInsertion(root, x);
} finally {
unlockRoot();
}
}
break;
}
}
assert checkInvariants(root);
return null;
}
說明:此函數(shù)用于將指定的hash、key拗窃、value值添加到紅黑樹中瞎领,若已經(jīng)添加了,則返回null随夸,否則返回該結(jié)點九默。
2.5 treeifyBin()
private final void treeifyBin(Node<K,V>[] tab, int index) {
Node<K,V> b; int n, sc;
if (tab != null) { // 表不為空
if ((n = tab.length) < MIN_TREEIFY_CAPACITY) // table表的長度小于最小的長度
// 進行擴容,調(diào)整某個桶中結(jié)點數(shù)量過多的問題(由于某個桶中結(jié)點數(shù)量超出了閾值宾毒,則觸發(fā)treeifyBin)
tryPresize(n << 1);
else if ((b = tabAt(tab, index)) != null && b.hash >= 0) { // 桶中存在結(jié)點并且結(jié)點的hash值大于等于0
synchronized (b) { // 對桶中第一個結(jié)點進行加鎖
if (tabAt(tab, index) == b) { // 第一個結(jié)點沒有變化
TreeNode<K,V> hd = null, tl = null;
for (Node<K,V> e = b; e != null; e = e.next) { // 遍歷桶中所有結(jié)點
// 新生一個TreeNode結(jié)點
TreeNode<K,V> p =
new TreeNode<K,V>(e.hash, e.key, e.val,
null, null);
if ((p.prev = tl) == null) // 該結(jié)點前驅(qū)為空
// 設(shè)置p為頭結(jié)點
hd = p;
else
// 尾節(jié)點的next域賦值為p
tl.next = p;
// 尾節(jié)點賦值為p
tl = p;
}
// 設(shè)置table表中下標為index的值為hd
setTabAt(tab, index, new TreeBin<K,V>(hd));
}
}
}
}
}
說明:此函數(shù)用于將桶中的數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)化為紅黑樹驼修,其中,值得注意的是诈铛,當table的長度未達到閾值時乙各,會進行一次擴容操作,該操作會使得觸發(fā)treeifyBin操作的某個桶中的所有元素進行一
次重新分配幢竹,這樣可以避免某個桶中的結(jié)點數(shù)量太大耳峦。
2.6 addCount()
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) { // counterCells不為空或者比較交換失敗
CounterCell a; long v; int m;
// 無競爭標識
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) { //
fullAddCount(x, uncontended);
return;
}
if (check <= 1)
return;
s = sumCount();
}
if (check >= 0) {
Node<K,V>[] tab, nt; int n, sc;
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
int rs = resizeStamp(n);
if (sc < 0) {
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
transfer(tab, nt);
}
else if (U.compareAndSwapInt(this,SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);
s = sumCount();
}
}
}
說明:此函數(shù)主要完成binCount的值加1的操作。
2.7 get()
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
// 計算key的hash值
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) { // 表不為空并且表的長度大于0并且key所在的桶不為空
if ((eh = e.hash) == h) { // 表中的元素的hash值與key的hash值相等
if ((ek = e.key) == key || (ek != null && key.equals(ek))) // 鍵相等
// 返回值
return e.val;
}
else if (eh < 0) // 結(jié)點hash值小于0
// 在桶(鏈表/紅黑樹)中查找
return (p = e.find(h, key)) != null ? p.val : null;
while ((e = e.next) != null) { // 對于結(jié)點hash值大于0的情況
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
說明:get()根據(jù)key的hash值來計算在哪個桶中焕毫,再遍歷桶蹲坷,查找元素驶乾,若找到則返回該結(jié)點,否則冠句,返回null轻掩。