目標(biāo)
1、volatile如何保證內(nèi)存可見性
2绩卤、volatile如何禁止指令重排序
3叁鉴、內(nèi)存屏障
4土涝、內(nèi)存可見性
5、關(guān)于volatile的單例模式
一幌墓、內(nèi)存可見性
1.1 緩存一致性問題
??1但壮、現(xiàn)代計算機(jī)系統(tǒng)在存儲設(shè)備與處理器之間加了一層讀寫速度盡可能解決處理器運算速度的高速緩存來作為內(nèi)存與處理器之間的緩沖: 將運算需要使用到的數(shù)據(jù)復(fù)制到緩存中, 讓運算能快速進(jìn)行, 當(dāng)運算結(jié)束后再從緩存同步回內(nèi)存之中, 這樣處理器就無須等待緩慢的內(nèi)存讀寫.
??2、緩存一致性問題:
在多處理器系統(tǒng)中, 每個處理器都有自己的高速緩存, 而它們又共享同一主內(nèi)存, 當(dāng)多個處理器的運算任務(wù)都涉及到同一個塊主內(nèi)存區(qū)域時, 將可能導(dǎo)致各自的緩存數(shù)據(jù)不一致.
1.2 內(nèi)存模型
在特定的操作協(xié)議下, 對特定的內(nèi)存或高速緩存進(jìn)行讀寫訪問的過程抽象.
1.3 內(nèi)存可見性
??1常侣、一個CPU核心對數(shù)據(jù)的修改, 對其他CPU核心立即可見.
??2蜡饵、CPU修改數(shù)據(jù), 首先是對緩存的修改, 然后再同步回主存, 在同步回主存的時候, 如果其他CPU也緩存了這個數(shù)據(jù), 就會導(dǎo)致其他CPU緩存上的數(shù)據(jù)失效, 這樣當(dāng)其他CPU再去它的緩存讀取這個數(shù)據(jù)的時候, 就必須從主存重新獲取.
??3、實現(xiàn)原理一般是基于CPU的MESI協(xié)議
, 其中E表示獨占Exclusive, S表示Shared, M表示Modify, I表示Invalid, 如果一個CPU核心修改了數(shù)據(jù), 那么這個CPU核心的數(shù)據(jù)狀態(tài)就會更新為M, 同時其他核心上的數(shù)據(jù)狀態(tài)更新為I, 這個是通過CPU多核之間的嗅探機(jī)制實現(xiàn)的.
1.4 MESI(緩存一致性)
Modify胳施、Exclusive溯祸、Shared、Invalid
, 當(dāng)CPU寫數(shù)據(jù)時, 如果發(fā)現(xiàn)操作的變量是共享變量, 即在其他CPU中也存在該變量的副本, 會發(fā)出信號通知其他CPU將該變量的緩存行為置為無效狀態(tài), 因此當(dāng)其他CPU需要讀取這個變量時, 發(fā)現(xiàn)自己緩存中緩存的該變量的緩存行是無效的, 那么它就會從內(nèi)存中重新讀取.
1.5 嗅探機(jī)制
1舞肆、例如在x86處理器下, 將volatile變量修飾的共享變量的Java代碼轉(zhuǎn)換成匯編代碼, 發(fā)現(xiàn)會多了lock修飾.
2焦辅、Lock前綴的指令在多核處理器下會引發(fā)以下事情.
3、將當(dāng)前處理器緩存行的數(shù)據(jù)寫回到系統(tǒng)內(nèi)存
4椿胯、這個寫回內(nèi)存的操作將會使其它CPU里緩存了該內(nèi)存地址的數(shù)據(jù)無效.
5筷登、原理分析:
為了提高處理速度, 處理器不直接和內(nèi)存進(jìn)行通信, 而是先將系統(tǒng)內(nèi)存的數(shù)據(jù)讀到內(nèi)部緩存(L1, L2或其它)后再進(jìn)行操作, 但操作完就不知道何時會寫到內(nèi)存. 如果對聲明了volatile的變量進(jìn)行寫操作, JVM會向處理器發(fā)送一條lock前綴的指令. 將這個變量所在的緩存行的數(shù)據(jù)寫回到系統(tǒng)內(nèi)存. 在多處理器下, 為了保證各個處理器緩存是一致的, 就會實現(xiàn)緩存一致性協(xié)議, 每個處理器通過嗅探
在總線上傳播的數(shù)據(jù)來檢查自己的緩存的值是否過期, 當(dāng)處理器發(fā)現(xiàn)自己緩存行對應(yīng)的內(nèi)存地址被修改, 就會將當(dāng)前處理器緩存行設(shè)置成無效狀態(tài), 當(dāng)處理器對這個數(shù)據(jù)進(jìn)行修改操作的時候, 會重新匆匆系統(tǒng)內(nèi)存中把數(shù)據(jù)讀到處理器緩存里.
1.6 volatile兩條實現(xiàn)原則
1、Lock前綴指令會引起處理器緩存回寫到內(nèi)存:
??Lock前綴指令導(dǎo)致在執(zhí)行指令期間, 聲言處理器的#LOCK信號. 在多處理器環(huán)境中, LOCK#信號確保在聲言該信號期間, 處理器可以獨占任何共享內(nèi)存. 但是在最近的處理器里, LOCK#信號一般不鎖總線, 而是鎖緩存, 畢竟鎖總線的開銷比較大. 對于Intel486和Pentium處理器, 在鎖操作時, 總是在總線上聲言LOCK#信號. 但在P6和目前的處理器中, 如果訪問的內(nèi)存區(qū)域已經(jīng)緩存在處理器內(nèi)部, 則不會聲言LOCK#信號. 相反, 它會鎖定這塊內(nèi)存區(qū)域的緩存并回寫到內(nèi)存, 并使用緩存一致性機(jī)制來確保修改的原子性, 此操作被稱為"緩存鎖定", 緩存一致性機(jī)制會阻止同時修改由兩個以上處理器緩存的內(nèi)存區(qū)域數(shù)據(jù).
2压状、一個處理器的緩存回寫到內(nèi)存會導(dǎo)致其它的緩存無效:
??IA-32處理器和Intel64處理器使用MESI控制協(xié)議去維護(hù)內(nèi)部緩存和其他處理器緩存的一致性. 在多核處理器系統(tǒng)中進(jìn)行操作的時候, IA-32和Intel 64處理器能嗅探其他處理器訪問系統(tǒng)內(nèi)存和它們的內(nèi)部緩存, 處理器使用嗅探技術(shù)保證它的內(nèi)部緩存仆抵、系統(tǒng)內(nèi)存和其他處理器的緩存的數(shù)據(jù)在總線上保持一致. 通過嗅探一個處理器來檢測其他處理器打算寫內(nèi)存地址, 而這個地址當(dāng)前處于共享狀態(tài), 那么正在嗅探的處理器將使它的緩存行無效, 在下次訪問相同內(nèi)存地址時, 強(qiáng)制執(zhí)行緩存行填充.
1.7 Volatile與原子性的關(guān)系
Volatile限定的是從緩存讀取時刻的校驗.
1.8 原子性
1.9 volatile單例模式
public class Instance {
private static volatile Instance instance;
private Instance() {}
public static Instance getInstance() {
if (instance == null) {
instance = new Instance();
}
}
}
上面代碼分成三步原子指令:
1跟继、new指令申請內(nèi)存;
2、在申請的內(nèi)存中進(jìn)行Instance的初始化;
3镣丑、將申請的內(nèi)存地址的引用賦值給instance變量;
雖然volatile可以禁止指令重排序, 讓上面三個指令有序執(zhí)行, 但是問題是volatile并不能保證原子性, 所以上面代碼中可能出現(xiàn)的問題是當(dāng)Thread-A執(zhí)行到第二步進(jìn)行new Instance初始化時, 此時還沒有將地址值賦給instance變量, 所以Thread-B此時看到的instance==null再次進(jìn)入if中執(zhí)行new Instance()操作.
所以假設(shè)上面代碼被兩個線程執(zhí)行, new Instance()會執(zhí)行兩次
參考文章:
https://blog.csdn.net/lsunwing/article/details/83154208
http://www.pianshen.com/article/7227186495/
https://blog.csdn.net/weixin_38623001/article/details/79167596