一俯画、內(nèi)存模型 </br>
??????程序運(yùn)行過程中的臨時(shí)數(shù)據(jù)存放在主內(nèi)存(物理內(nèi)存)當(dāng)中的耕魄。而從內(nèi)存中讀寫的速度跟CPU執(zhí)行指令的速度比起來要慢的多添谊,如果任何時(shí)候?qū)?shù)據(jù)的操作都要通過和內(nèi)存的交互來進(jìn)行痪伦,會(huì)大大降低指令的執(zhí)行速度。因此CPU里面存在高速緩存遭贸。 </br>
??????在程序運(yùn)行的過程中戈咳,CPU會(huì)將運(yùn)算需要的數(shù)據(jù)從主存復(fù)制一份到其高速緩存中。CPU在進(jìn)行計(jì)算時(shí)壕吹,直接從其高速緩存讀寫數(shù)據(jù)著蛙。當(dāng)運(yùn)算結(jié)束之后,再將高速緩存的數(shù)據(jù)刷新到主存當(dāng)中算利。 </br>
??????當(dāng)一個(gè)變量在多個(gè)CPU都存在緩存時(shí),就有可能存在緩存不一致的問題泳姐。解決辦法: </br>
1)效拭、通過在總線加LOCK#鎖 </br>
??????因?yàn)镃PU和其他部件進(jìn)行通訊時(shí)都是通過總線進(jìn)行的,如果對(duì)總線加LOCK#鎖,阻塞了其他CPU對(duì)其他部件的訪問(如主存)缎患。從而使只有一個(gè)CPU能使用這個(gè)變量的內(nèi)存慕的。 </br>
??????但在鎖住總線期間,其他CPU無法訪問內(nèi)存挤渔,導(dǎo)致效率低下肮街。 </br>
2)、緩存一致性協(xié)議
??????Intel的MESI協(xié)議:當(dāng)CPU寫數(shù)據(jù)時(shí)判导,如果發(fā)現(xiàn)操作的數(shù)據(jù)時(shí)共享變量嫉父,即在其他CPU中也存在該變量的副本。則會(huì)發(fā)送信號(hào)通知其他CPU將該變量的緩存置為無效狀態(tài)眼刃。因此其他CPU需要讀取該變量時(shí)绕辖,發(fā)現(xiàn)自身緩存中該變量的緩存是無效的,則會(huì)從主存中重新獲取擂红。 </br>
二仪际、并發(fā)中的三個(gè)概念 </br>
??????1、原子性:即一個(gè)或多個(gè)操作昵骤,要么全部執(zhí)行完畢树碱,要么都不執(zhí)行。 </br>
??????2变秦、可見性:當(dāng)多個(gè)線程訪問同一個(gè)變量時(shí)成榜,一個(gè)線程修改了這個(gè)變量的值,其他線程能夠立即看到修改的值伴栓。 </br>
??????3伦连、有序性:程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。(處理器會(huì)考慮指令間的數(shù)據(jù)依賴關(guān)系來進(jìn)行重排序) </br>
?????? 如以下例子钳垮,代碼1和代碼2進(jìn)行重排序?qū)Y(jié)果并無影響惑淳,但代碼3必須在代碼1,2之后。即可能出現(xiàn)的排序順序是 1,2,3 或者 2,1,3 </br>
int a = 10;
int b = 17;
a = a + b;
三饺窿、Java內(nèi)存模型 </br>
??????Java虛擬機(jī)中定義一種Java內(nèi)存模型以屏蔽各個(gè)硬件平臺(tái)和操作系統(tǒng)的內(nèi)存訪問差異歧焦。</br>
??????Java內(nèi)存模型沒有限制執(zhí)行引擎使用處理器的寄存器或高速緩存來提升執(zhí)行指令,也沒有限制編譯器對(duì)指令進(jìn)行重排序肚医。即Java內(nèi)存模型也會(huì)出現(xiàn)緩存一致性問題和指令重排序的問題绢馍。</br>
??????Java內(nèi)存模型規(guī)定所有的變量都是存在主存中(類似物理內(nèi)存),每個(gè)線程都有自己的工作內(nèi)存(類似CPU的高速緩存)肠套。線程對(duì)變量的所有操作必須在工作內(nèi)存中進(jìn)行舰涌,而不能直接對(duì)主存進(jìn)行操作。并且每個(gè)線程不能訪問其他線程的工作內(nèi)存你稚。</br>
1瓷耙、原子性
x = 10 //語句1
y = x //語句2
x++ //語句3
x = x + 1 //語句4
??????只有語句1是原子性操作朱躺,其他三個(gè)語句都不是原子性操作。</br>
??????語句1直接將10賦值給x,也就是說線程執(zhí)行這個(gè)語句時(shí)會(huì)直接將數(shù)值10寫入到工作內(nèi)存中搁痛。而其他語句實(shí)際上包含2個(gè)操作长搀,先去讀取x的值,再將x的值寫入工作內(nèi)存鸡典。</br>
??????總結(jié):Java內(nèi)存模型只保證簡單的讀取和賦值(變量之間相互賦值不是原子操作)才是原子操作源请。想要保證大范圍操作的原子性,需要通過synchronized和Lock實(shí)現(xiàn)彻况,確保任意時(shí)刻只有一個(gè)線程執(zhí)行該代碼塊谁尸。</br>
2、可見性
??????Java提供volatile關(guān)鍵字保證可見性疗垛。</br>
??????當(dāng)一個(gè)共享變量被volatile修飾時(shí)症汹,它會(huì)保證修改的值會(huì)立即被更新到主存中,當(dāng)有其他線程需要讀取時(shí)贷腕,它會(huì)去主存讀取新值背镇。</br>
??????而普通的共享變量不能保證可見性,因?yàn)槠胀ü蚕碜兞勘恍薷暮笤笊眩裁磿r(shí)候?qū)懭胫鞔媸遣淮_定的瞒斩。當(dāng)其他線程去讀取,此時(shí)內(nèi)存可能還是原來的舊值涮总。因此無法保證可見性胸囱。</br>
??????通過synchronized和Lock也能夠保證可見性,synchronized和Lock能保證同一時(shí)刻只有一個(gè)線程獲取鎖瀑梗,然后執(zhí)行同步代碼烹笔,并且釋放鎖之前會(huì)將變量的修改刷新到主存當(dāng)中,因此可以確迸桌觯可見性谤职。</br>
3、有序性
??????在Java內(nèi)存模型中亿鲜,允許編譯器對(duì)指令進(jìn)行重排序允蜈,但是重排序不會(huì)影響到單線程的執(zhí)行,卻影響到多線程并發(fā)執(zhí)行的正確性蒿柳。</br>
比如new對(duì)象時(shí)饶套,會(huì)進(jìn)行三件事件:</br>
??????(1)、給實(shí)例分配內(nèi)存垒探;</br>
??????(2)妓蛮、調(diào)用構(gòu)造方法,初始化成員變量圾叼。</br>
??????(3)蛤克、將對(duì)象指向分配的內(nèi)存空間扔仓。</br>
而在JVM中(2)和(3)的順序是無法被保證的,只能通過volalite保證其有序性咖耘。
四、深入剖析volatile關(guān)鍵字 </br>
1)保證不同線程對(duì)變量進(jìn)行操作時(shí)的可見性(即一個(gè)線程修改某個(gè)變量的值撬码,這新值對(duì)其他線程來說是立即可見的)
2)禁止進(jìn)行指令重排序儿倒。
volatile的原理和實(shí)現(xiàn)機(jī)制:</br>
??????“觀察加入volalite關(guān)鍵字和沒有加入volalite關(guān)鍵字鎖生成的匯編代碼發(fā)現(xiàn),加入volatile關(guān)鍵字時(shí)呜笑,會(huì)多出一個(gè)lock前綴指令”</br>
??????lock前準(zhǔn)指令實(shí)際上相當(dāng)于一個(gè)內(nèi)存屏障夫否,內(nèi)存屏障會(huì)提供3個(gè)功能:</br>
??????1、確保指令重排序時(shí)叫胁,不會(huì)把其后面的指令排到內(nèi)存屏障之前的位置凰慈,也不會(huì)把前面的指令排到內(nèi)存屏障的后面。</br>
??????2驼鹅、強(qiáng)制對(duì)緩存的修改操作立即寫入內(nèi)存微谓。</br>
??????3、如果是寫操作输钩,會(huì)導(dǎo)致其他CPU中對(duì)應(yīng)的緩存無效豺型。</br>
五、使用volatile關(guān)鍵字的場景 </br>
??????synchronized關(guān)鍵字是防止多個(gè)線程同時(shí)執(zhí)行一段代碼买乃,但會(huì)影響執(zhí)行效率姻氨。而volatile關(guān)鍵字在某些情況下性能優(yōu)于synchronized,但不能替代synchronized,因?yàn)関olatile不能提供原子性剪验。</br>
??????1)對(duì)變量的寫操作不依賴于當(dāng)前值</br>
??????2)該變量沒有包含在具有其他變量的不變式中</br>
個(gè)人總結(jié):
??????配合計(jì)算機(jī)的內(nèi)存模型肴焊,可以很好的理解Java的內(nèi)存模型。以及了解到功戚,可見性在高并發(fā)時(shí)的重要性娶眷。</br>
??????在確保某個(gè)變量(比如某個(gè)flag值)在單線程進(jìn)行修改操作時(shí),可以使用volatile確保該變量的可見性疫铜。相比synchronized效率有所提升茂浮。</br>
??????在單例DCL中,synchronized時(shí)確保了變量的有序性壳咕、可見性席揽。但并沒有確保有序性,這時(shí)就需要將變量修飾成volatile來確保有序性</br>
public class Singleton{
private volatile static Singleton mInstance;
private Singleton() {}
public static Singleton getInstance(){
if( mInstance == null){// 語句A
synchronized(Singleton.class){
if( mInstance == null){ // 語句B
mInstance = new Singleton();
}
}
}
return mInstance;
}
}
因?yàn)楫?dāng)指令進(jìn)行重排序后會(huì)出現(xiàn)以下情況:</br>
(1)谓厘、給Singleton的實(shí)例分配內(nèi)存幌羞;</br>
(3)、將mInstance對(duì)象指向分配的內(nèi)存空間竟稳。</br>
(2)属桦、調(diào)用Singleton()的構(gòu)造方法熊痴,初始化成員變量。</br>
??????當(dāng)多線程并發(fā)時(shí)聂宾,線程A先運(yùn)行到語句A中果善,mInstance是null,線程A進(jìn)行單例對(duì)象的初始化。但因?yàn)橹噶钪嘏判蛳敌常霈F(xiàn)了(1)(3)(2)的情況巾陕,當(dāng)線程A還沒執(zhí)行完(2),也就是還沒初始化完單例對(duì)象時(shí)纪他。線程B運(yùn)行到語句A鄙煤。此時(shí)單例對(duì)象已不為null,自然語句A為false茶袒,線程B會(huì)返回一個(gè)還沒初始化完畢的mInstance對(duì)象梯刚。
參考文章:
1、https://www.cnblogs.com/dolphin0520/p/3920373.html </br>
2薪寓、《Android源碼設(shè)計(jì)模式》——《單例設(shè)計(jì)模式》