我們知道,ConcurrentHashmap(1.8)這個并發(fā)調(diào)集結(jié)構(gòu)是線程安全的,當(dāng)你看到源碼的get操作時,會發(fā)現(xiàn)get操作全程是沒有加任何鎖的,這也是這篇博文討論的問題——為什么它不需要加鎖呢?
ConcurrentHashMap的簡介
我想有基礎(chǔ)的同學(xué)知道在jdk1.7中是選用Segment+HashEntry+ReentrantLock的方式進(jìn)行完成的,而1.8中拋棄了Segment臃腫的規(guī)劃,取而代之的是選用Node+CAS+Synchronized來確保并發(fā)安全進(jìn)行完成。
JDK1.8的完成下降鎖的粒度,JDK1.7版本鎖的粒度是根據(jù)Segment的,包含多個HashEntry,而JDK1.8鎖的粒度便是HashEntry(首節(jié)點)
JDK1.8版本的數(shù)據(jù)結(jié)構(gòu)變得愈加簡略,使得操作也愈加明晰流暢,因為現(xiàn)已運用synchronized來進(jìn)行同步,所以不需要分段鎖的概念,也就不需要Segment這種數(shù)據(jù)結(jié)構(gòu)了,因為粒度的下降,完成的復(fù)雜度也增加了
JDK1.8運用紅黑樹來優(yōu)化鏈表,根據(jù)長度很長的鏈表的遍歷是一個很漫長的進(jìn)程,而紅黑樹的遍歷效率是很快的,替代必定閾值的鏈表,這樣構(gòu)成一個最佳拍檔
img
get操作源碼
首先計算hash值,定位到該table索引方位,假如是首節(jié)點契合就回來
假如遇到擴(kuò)容的時分,會調(diào)用標(biāo)志正在擴(kuò)容節(jié)點ForwardingNode的find方法,查找該節(jié)點,匹配就回來
以上都不契合的話,就往下遍歷節(jié)點,匹配就回來,否則最終就回來null
//會發(fā)現(xiàn)源碼中沒有一處加了鎖publicVget(Objectkey){
Node[]tab;Nodee,p;intn,eh;Kek;inth=spread(key.hashCode());//計算hashif((tab=table)!=null&&(n=tab.length)>0&&
(e=tabAt(tab,(n-1)&h))!=null){//讀取首節(jié)點的Node元素if((eh=e.hash)==h){//假如該節(jié)點便是首節(jié)點就回來if((ek=e.key)==key||(ek!=null&&key.equals(ek)))returne.val;
}//hash值為負(fù)值表示正在擴(kuò)容,這個時分查的是ForwardingNode的find方法來定位到nextTable來//eh=-1,闡明該節(jié)點是一個ForwardingNode,正在搬遷,此刻調(diào)用ForwardingNode的find方法去nextTable里找。//eh=-2,闡明該節(jié)點是一個TreeBin,此刻調(diào)用TreeBin的find方法遍歷紅黑樹,因為紅黑樹有或許正在旋轉(zhuǎn)變色,所以find里會有讀寫鎖。//eh>=0,闡明該節(jié)點下掛的是一個鏈表,直接遍歷該鏈表即可。elseif(eh<0)return(p=e.find(h,key))!=null?p.val:null;while((e=e.next)!=null){//既不是首節(jié)點也不是ForwardingNode,那就往下遍歷if(e.hash==h&&
((ek=e.key)==key||(ek!=null&&key.equals(ek))))returne.val;
}
}returnnull;
}
get沒有加鎖的話,ConcurrentHashMap是如何確保讀到的數(shù)據(jù)不是臟數(shù)據(jù)的呢?
volatile登場
關(guān)于可見性,Java供給了volatile關(guān)鍵字來確??梢娦浴⒂行蛐?。但不確保原子性。
一般的同享變量不能確保可見性,因為一般同享變量被修正之后,什么時分被寫入主存是不確定的,當(dāng)其他線程去讀取時,此刻內(nèi)存中或許仍是本來的舊值,因此無法確??梢娦浴?br />
volatile關(guān)鍵字關(guān)于根本類型的修正能夠在隨后對多個線程的讀保持共同,但是關(guān)于引證類型如數(shù)組,實體bean,僅僅確保引證的可見性,但并不確保引證內(nèi)容的可見性。。
禁止進(jìn)行指令重排序。
布景:為了提高處理速度,處理器不直接和內(nèi)存進(jìn)行通訊,而是先將系統(tǒng)內(nèi)存的數(shù)據(jù)讀到內(nèi)部緩存(L1,L2或其他)后再進(jìn)行操作,但操作完不知道何時會寫到內(nèi)存。
假如對聲明了volatile的變量進(jìn)行寫操作,JVM就會向處理器發(fā)送一條指令,將這個變量地點緩存行的數(shù)據(jù)寫回到系統(tǒng)內(nèi)存。但是,就算寫回到內(nèi)存,假如其他處理器緩存的值仍是舊的,再履行計算操作就會有問題。
在多處理器下,為了確保各個處理器的緩存是共同的,就會完成緩存共同性協(xié)議,當(dāng)某個CPU在寫數(shù)據(jù)時,假如發(fā)現(xiàn)操作的變量是同享變量,則會通知其他CPU告知該變量的緩存行是無效的,因此其他CPU在讀取該變量時,發(fā)現(xiàn)其無效會從頭從主存中加載數(shù)據(jù)。
img
總結(jié)下來:
第一:運用volatile關(guān)鍵字會強制將修正的值立即寫入主存;
第二:運用volatile關(guān)鍵字的話,當(dāng)線程2進(jìn)行修正時,會導(dǎo)致線程1的作業(yè)內(nèi)存中緩存變量的緩存行無效(反映到硬件層的話,便是CPU的L1或許L2緩存中對應(yīng)的緩存行無效);
第三:因為線程1的作業(yè)內(nèi)存中緩存變量的緩存行無效,所以線程1再次讀取變量的值時會去主存讀取。
是加在數(shù)組上的volatile嗎?
/**
*Thearrayofbins.Lazilyinitializeduponfirstinsertion.
*Sizeisalwaysapoweroftwo.Accesseddirectlybyiterators.
*/
transientvolatileNode[]table;
我們知道volatile能夠潤飾數(shù)組的,只是意思和它表面上看起來的姿態(tài)不同。舉個栗子,volatileintarray[10]是指array的地址是volatile的而不是數(shù)組元素的值是volatile的.
用volatile潤飾的Node
get操作能夠無鎖是因為Node的元素val和指針next是用volatile潤飾的,在多線程環(huán)境下線程A修正結(jié)點的val或許新增節(jié)點的時分是對線程B可見的。
staticclassNode<K,V>implementsMap.Entry<K,V>{finalinthash;finalKkey;//能夠看到這些都用了volatile潤飾volatileVval;volatileNodenext;
Node(inthash,Kkey,Vval,Nodenext){this.hash=hash;this.key=key;this.val=val;this.next=next;
}publicfinalKgetKey(){returnkey;}publicfinalVgetValue(){returnval;}publicfinalinthashCode(){returnkey.hashCode()^val.hashCode();}publicfinalStringtoString(){returnkey+”=”+val;}publicfinalVsetValue(Vvalue){thrownewUnsupportedOperationException();
}publicfinalbooleanequals(Objecto){
Objectk,v,u;Map.Entrye;return((oinstanceofMap.Entry)&&
(k=(e=(Map.Entry)o).getKey())!=null&&
(v=e.getValue())!=null&&
(k==key||k.equals(key))&&
(v==(u=val)||v.equals(u)));
}/**
*Virtualizedsupportformap.get();overriddeninsubclasses.
*/Nodefind(inth,Objectk){
Nodee=this;if(k!=null){do{
Kek;if(e.hash==h&&
((ek=e.key)==k||(ek!=null&&k.equals(ek))))returne;
}while((e=e.next)!=null);
}returnnull;
}
}
既然volatile潤飾數(shù)組對get操作沒有作用那加在數(shù)組上的volatile的目的是什么呢?
其實便是為了使得Node數(shù)組在擴(kuò)容的時分對其他線程具有可見性而加的volatile
總結(jié)
在1.8中ConcurrentHashMap的get操作全程不需要加鎖,這也是它比其他并發(fā)調(diào)集比如hashtable、用Collections.synchronizedMap()包裝的hashmap;安全效率高的原因之一。
get操作全程不需要加鎖是因為Node的成員val是用volatile潤飾的和數(shù)組用volatile潤飾沒有關(guān)系。
數(shù)組用volatile潤飾主要是確保在數(shù)組擴(kuò)容的時分確??梢娦?。
廣州天河區(qū)珠江新城富力盈力大廈北塔2706
020-38013166(網(wǎng)站咨詢專線)
400-001-5281 (售后服務(wù)熱線)
深圳市坂田十二橡樹莊園F1-7棟
Site/ http://www.szciya.com
E-mail/ itciya@vip.163.com
品牌服務(wù)專線:400-001-5281
長沙市天心區(qū)芙蓉中路三段398號新時空大廈5樓
聯(lián)系電話/ (+86 0731)88282200
品牌服務(wù)專線/ 400-966-8830
旗下運營網(wǎng)站:
Copyright ? 2016 廣州思洋文化傳播有限公司,保留所有權(quán)利。 粵ICP備09033321號