前言
并發(fā)編程中砍的,經(jīng)常使用到syncronized和volatile同步元語浅役。相比較syncronized银萍,volatile可以說是Java虛擬機(jī)提供的輕量級同步機(jī)制返顺,因?yàn)樗粫鹁€程上下文切換和調(diào)度。使用volatile的一個(gè)關(guān)鍵目的是保證共享變量的可見性站绪。本文將從CPU指令的角度了解Volatile如何實(shí)現(xiàn)共享變量的可見性
CPU存儲層次構(gòu)成
眾所周知遭铺,由于CPU運(yùn)行速度非常快崇众,而主內(nèi)存相對來說很慢掂僵,為了提高CPU運(yùn)行效率航厚,CPU并不直接訪問主內(nèi)存顷歌,兩者之間設(shè)計(jì)有多層緩存結(jié)構(gòu)。
下圖是來至《深入理解計(jì)算機(jī)系統(tǒng)》書中關(guān)于CPU的高手緩存層次結(jié)構(gòu):
JAVA內(nèi)存模型(Java Memory Model)
JAVA內(nèi)存模型是JAVA虛擬機(jī)用來屏蔽硬件和操作系統(tǒng)內(nèi)存讀取差異幔睬,以達(dá)到各個(gè)平臺下都能達(dá)到一致的內(nèi)存訪問效果眯漩。
JAVA內(nèi)存模型和CPU存儲結(jié)構(gòu)對應(yīng)關(guān)系
理想中的CPU存儲結(jié)構(gòu)是所有CPU共享同一個(gè)Cache
這樣,當(dāng)其中一個(gè)CPU進(jìn)行寫操作,而另一個(gè)CPU進(jìn)行讀操作赦抖,總是能讀到正確的值舱卡。
但是,會極大的降低系統(tǒng)的運(yùn)算速度队萤,因?yàn)樗蠧PU均需要串行的訪問Cache以獲取數(shù)據(jù)轮锥,大部分時(shí)間均在等待Cache使用權(quán)。
如果引入多個(gè)Cache要尔,就會涉及到Cache的一致性問題舍杜。
所以,Cache的一致性問題赵辕,不是因?yàn)槎郈PU導(dǎo)致既绩,而是多Cache導(dǎo)致。
實(shí)現(xiàn)volatile依賴的CPU指令
public class Test {
private static volatile int a = 1;
public static void test() {
a = 2;
}
public static void main(String [] args) {
test();
}
}
我們添加hsdis插件到JRE的lib目錄后还惠,可以對上述代碼進(jìn)行反匯編:
javac Test.java
java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly Test
參數(shù)+PrintAssembly 的意思是打印出匯編代碼
結(jié)果如下:
0x000000011a5ddf25: callq 0x000000010cb439f0 ; {runtime_call}
0x000000011a5ddf2a: vzeroupper
0x000000011a5ddf2d: movl $0x5,0x270(%r15)
0x000000011a5ddf38: lock addl $0x0,(%rsp)
0x000000011a5ddf3d: cmpl $0x0,-0xd4ec2e7(%rip) # 0x000000010d0f1c60
lock 指令就是CPU實(shí)現(xiàn)volatile可見性的秘密所在饲握。通過查IA-32架
構(gòu)軟件開發(fā)者手冊。
8.1.4 Effects of a LOCK Operation on Internal Processor Caches
For the Intel486 and Pentium processors, the LOCK# signal is always asserted on the bus during a LOCK operation,
even if the area of memory being locked is cached in the processor.
For the P6 and more recent processor families, if the area of memory being locked during a LOCK operation is
cached in the processor that is performing the LOCK operation as write-back memory and is completely contained
in a cache line, the processor may not assert the LOCK# signal on the bus. Instead, it will modify the memory location internally and allow it’s cache coherency mechanism to ensure that the operation is carried out atomically. This
operation is called “cache locking.” The cache coherency mechanism automatically prevents two or more processors that have cached the same area of memory from simultaneously modifying data in that area.
對于Intel486和
Pentium處理器蚕键,在鎖操作時(shí)救欧,總是在總線上聲言LOCK#信號。但在P6和目前的處理器中锣光,如果
訪問的內(nèi)存區(qū)域已經(jīng)緩存在處理器內(nèi)部颜矿,則不會聲言LOCK#信號。相反嫉晶,它會鎖定這塊內(nèi)存區(qū)
域的緩存并回寫到內(nèi)存骑疆,并使用緩存一致性機(jī)制來確保修改的原子性,此操作被稱為“緩存鎖定”替废,緩存一致性機(jī)制會阻止同時(shí)修改由兩個(gè)以上處理器緩存的內(nèi)存區(qū)域數(shù)據(jù)箍铭。
.
in the Pentium and P6 family processors, if through snooping one processor
detects that another processor intends to write to a memory location that it currently has cached in shared state,
the snooping processor will invalidate its cache line forcing it to perform a cache line fill the next time it accesses
the same memory location.
在Pentium和P6 family處理器中,如果通過嗅探一個(gè)處理
器來檢測其他處理器打算寫內(nèi)存地址椎镣,而這個(gè)地址當(dāng)前處于共享狀態(tài)诈火,那么正在嗅探的處理
器將使它的緩存行無效,在下次訪問相同內(nèi)存地址時(shí)状答,強(qiáng)制執(zhí)行緩存行填充
一個(gè)處理器的緩存回寫到內(nèi)存會導(dǎo)致其他處理器的緩存無效
上述引用總結(jié)為volatile的兩條實(shí)現(xiàn)原則:
對緩存行加鎖內(nèi)容的修改會導(dǎo)致修改后的值馬上回寫內(nèi)存
一個(gè)處理器的緩存回寫到內(nèi)存會導(dǎo)致其他處理器的緩存無效
緩存一致性協(xié)議
一致性緩存:所有緩存副本中的值都相同冷守,多個(gè)CPU處理器共享緩存并且更改共享數(shù)據(jù)時(shí),更改必須廣播到所有緩存副本惊科。
在處理器中拍摇,嗅探是一致性緩存的常見的機(jī)制。工作原理是通過總線監(jiān)聽所有的共享數(shù)據(jù)的操作馆截,當(dāng)修改共享數(shù)據(jù)的事件發(fā)生時(shí)充活,所有監(jiān)聽處理器都會檢查是否存在當(dāng)前共享數(shù)據(jù)的副本蜂莉,如果存在,監(jiān)聽處理器會執(zhí)行刷新或者直接失效緩存副本操作混卵。
實(shí)現(xiàn)方式
緩存將具有三個(gè)額外的位:
V(可用)D(臟位映穗,表示高速緩存中的數(shù)據(jù)與內(nèi)存中的數(shù)據(jù)不同)S(共享)
讀未命中:
CPU_A本地緩存讀未命中,會廣播到監(jiān)聽總線上幕随,其他所有CPU監(jiān)聽處理器會檢查蚁滋,如果緩存了該地址,并且緩存處于“D(臟位)”赘淮,將狀態(tài)改成有效枢赔,同時(shí)發(fā)送副本到請求節(jié)點(diǎn)。
寫未命中:
CPU_A嘗試更新本地緩存拥知,但是更新并不在主存中踏拜。其他所有CPU監(jiān)聽處理器
可確保將其他高速緩存中的所有副本都設(shè)置為“無效”。
以上是主要的場景低剔,具體實(shí)現(xiàn)有 MESI 協(xié)議等速梗。
總結(jié)
基于CPU緩存一致性協(xié)議,JVM實(shí)現(xiàn)了volatile的可見性襟齿。當(dāng)一個(gè)變量被volatile修飾時(shí)姻锁,那么對它的修改會立刻刷新到主存,當(dāng)其它線程需要讀取該變量時(shí)猜欺,會去內(nèi)存中讀取新值位隶。