JDK7的ConcurrentHashMap使用Segment分段鎖機(jī)制猪瞬,但是在JDK8采用CAS自旋》+synchronized鎖機(jī)制。并且加鎖的粒度更小话浇,直接鎖Node數(shù)組的第一個(gè)元素增蹭,即實(shí)現(xiàn)了對(duì)單個(gè)key加鎖杆逗。
private void analysisPo2Cache(Class < ?>clazz) {
//維護(hù)緩存
cache.computeIfAbsent(clazz.getName(), key - >{
HashSet < Field > values = new HashSet < >();
Field[] declaredFields = clazz.getDeclaredFields();
Field.setAccessible(declaredFields, true);
for (Field field: declaredFields) {
Class < ?>fieldClass = field.getType();
//判斷是否含有List的屬性
if (List.class.isAssignableFrom(fieldClass)) {
values.add(field);
}
}
return values;
});
}
源碼分析:
JDK8的Map的數(shù)據(jù)結(jié)構(gòu)是Node數(shù)組和鏈表+紅黑樹学少。
線程安全的核心思想:CAS+ synchronized鎖對(duì)Node數(shù)組上第一個(gè)節(jié)點(diǎn)加鎖剪个。
- 多個(gè)線程均會(huì)創(chuàng)建
ReservationNode
節(jié)點(diǎn),均執(zhí)行執(zhí)行synchronized(r)
(因?yàn)殒i的是對(duì)象版确,所以所以線程都會(huì)執(zhí)行casTabAt
方法)扣囊。CAS失敗的線程釋放鎖,但是CAS成功的線程將執(zhí)行l(wèi)amda表達(dá)式(此時(shí)阀坏,持有synchronized(r)的鎖)如暖。 - 失敗的線程將再次自旋笆檀,在
(f = tabAt(tab, i = (n - 1) & h)) == null)
方法中獲取key所在位置的Node對(duì)象(lamda表達(dá)式?jīng)]執(zhí)行完畢忌堂,那么Node對(duì)象就是r,但是r已經(jīng)被synchronized)酗洒,此時(shí)synchronized(f)
會(huì)被阻塞士修,等待synchronized(r)
釋放鎖枷遂。當(dāng)?shù)鹊?code>synchronized(r)釋放鎖后,key所在位置的Node對(duì)象會(huì)發(fā)生變化棋嘲,會(huì)再次通過tabAt(tab, i) == f
判斷酒唉。開啟下次自旋。
public V computeIfAbsent(K key, Function < ?super K, ?extends V > mappingFunction) {
//key的lamda表達(dá)式都不能為空
if (key == null || mappingFunction == null) throw new NullPointerException();
//右移16位計(jì)算有效的hash值
int h = spread(key.hashCode());
V val = null;
int binCount = 0;
//開啟自旋
for (Node <K, V> [] tab = table;;) {
Node <K,V> f;
int n,i,fh;
//初次put時(shí)沸移,因?yàn)閏oncurrenthashmap在構(gòu)造時(shí)沒有初始化table痪伦,所以先初始化table
if (tab == null || (n = tab.length) == 0) tab = initTable();
//n為數(shù)組長(zhǎng)度,低位有效二次hash后計(jì)算出數(shù)組的下標(biāo)雹锣,并獲取該下標(biāo)的node對(duì)象(線程不安全)网沾,且node所在的位置為null
else if ((f = tabAt(tab, i = (n - 1) & h)) == null) {
//創(chuàng)建空節(jié)點(diǎn)(預(yù)定節(jié)點(diǎn))
Node <K,V> r = new ReservationNode <K,V> ();
//【神操作】這里加synchronized是為了CAS失敗的線程在此自旋的時(shí)候在synchronized(f)處阻塞,等待CAS維護(hù)value釋放鎖蕊爵。
synchronized(r) {
//CAS辉哥,使用預(yù)定節(jié)點(diǎn)去替換null(只有一個(gè)線程成功)
if (casTabAt(tab, i, null, r)) {
//成功進(jìn)入的線程維護(hù)value。
binCount = 1;
Node <K,V> node = null;
try {
//執(zhí)行l(wèi)amda表達(dá)式
if ((val = mappingFunction.apply(key)) != null)
node = new Node <K,V> (h, key, val, null);
} finally {
//設(shè)置內(nèi)存的值
setTabAt(tab, i, node);
}
}
}
//CAS進(jìn)入的線程結(jié)束自旋攒射,其他線程繼續(xù)向下執(zhí)行醋旦。
if (binCount != 0) break;
} else if ((fh = f.hash) == MOVED)
//node的hash值為-1,表示正在擴(kuò)容会放,本次操作會(huì)幫助擴(kuò)容【多線程完成擴(kuò)容】
tab = helpTransfer(tab, f);
else {
//CAS加鎖失敗后的線程饲齐,自旋最終會(huì)到達(dá)此處
boolean added = false;
//【神操作2】即處理了lamda表達(dá)式執(zhí)行慢,sync(r)方法持有鎖咧最,也解決了并發(fā)插入線程安全箩张。
synchronized(f) {
//【神操作3】sync(r)釋放鎖后,線程才會(huì)獲取到到此處的鎖窗市,此時(shí)f一定不是tabAt(tab, i) 先慷。需要重新自旋。
if (tabAt(tab, i) == f) {
//fh為該node的hash值咨察,如果hash值為正數(shù)论熙,說明該下標(biāo)是按鏈表存儲(chǔ)的
if (fh >= 0) {
binCount = 1;
//遍歷鏈表(哈希桶)
for (Node < K, V > e = f;; ++binCount) {
K ek;V ev;
//若key相同,直接返回摄狱,結(jié)束自旋脓诡。
if (e.hash == h && ((ek = e.key) == key || (ek != null && key.equals(ek)))) {
val = e.val;
break;
}
Node < K,V > pred = e;
//如果鏈表中不存在這個(gè)key,則添加到鏈表末尾
if ((e = e.next) == null) {
if ((val = mappingFunction.apply(key)) != null) {
added = true;
pred.next = new Node < K,V > (h, key, val, null);
}
break;
}
}
} else if (f instanceof TreeBin) {
//紅黑樹的情況
binCount = 2;
TreeBin < K,V > t = (TreeBin < K, V > ) f;
TreeNode < K,V > r,
p;
//節(jié)點(diǎn)加到紅黑樹
if ((r = t.root) != null && (p = r.findTreeNode(h, key, null)) != null) val = p.val;
else if ((val = mappingFunction.apply(key)) != null) {
added = true;
t.putTreeVal(h, key, val);
}
}
}
}
//加鎖成功的線程binCount會(huì)變?yōu)?
if (binCount != 0) {
//bitCount>8媒役,嘗試將鏈表轉(zhuǎn)換為紅黑樹
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (!added)
return val;
//結(jié)束自旋
break;
}
}
}
//總node+1祝谚,該方法中滿足條件會(huì)進(jìn)行擴(kuò)容。
if (val != null) addCount(1L, binCount);
return val;
}
總結(jié):
【廣撒網(wǎng)】若key對(duì)應(yīng)的第一個(gè)Node節(jié)點(diǎn)為null酣衷,每個(gè)線程均會(huì)對(duì)新建的空節(jié)點(diǎn)對(duì)象加鎖交惯,但只有CAS成功的線程一直持有對(duì)象鎖,其他線程釋放鎖,開啟下次自旋席爽。
【實(shí)時(shí)更新】若key對(duì)應(yīng)的第一個(gè)Node節(jié)點(diǎn)不為null意荤,線程獲取最新的Node節(jié)點(diǎn)對(duì)象(但Node節(jié)點(diǎn)可能已經(jīng)被加鎖),嘗試對(duì)Node加鎖去put數(shù)據(jù)只锻。