Java 內(nèi)存模型中的可見(jiàn)性础钠、原子性和有序性。
可見(jiàn)性:可見(jiàn)性叉谜,是指線程之間的可見(jiàn)性旗吁,一個(gè)線程修改的狀態(tài)對(duì)另一個(gè)線程是可見(jiàn)的。
原子性:原子是世界上的最小單位停局,具有不可分割性很钓。比如 a=0;(a非long和double類(lèi)型) 這個(gè)操作是不可分割的董栽,那么我們說(shuō)這個(gè)操作時(shí)原子操作码倦。再比如:a++; 這個(gè)操作實(shí)際是a = a + 1锭碳;是可分割的袁稽,所以他不是一個(gè)原子操作。非原子操作都會(huì)存在線程安全問(wèn)題擒抛,需要我們使用同步技術(shù)(sychronized)來(lái)讓它變成一個(gè)原子操作推汽。
有序性:Java 語(yǔ)言提供了 volatile 和 synchronized 兩個(gè)關(guān)鍵字來(lái)保證線程之間操作的有序性补疑,volatile 是因?yàn)槠浔旧戆敖怪噶钪嘏判颉钡恼Z(yǔ)義,synchronized 是由“一個(gè)變量在同一個(gè)時(shí)刻只允許一條線程對(duì)其進(jìn)行 lock 操作”這條規(guī)則獲得的歹撒,此規(guī)則決定了持有同一個(gè)對(duì)象鎖的兩個(gè)同步塊只能串行執(zhí)行莲组。
volatile的工作流程
volatile的底層原理
voliate緩存可見(jiàn)性實(shí)現(xiàn)原理:
底層實(shí)現(xiàn)主要是通過(guò)匯編lock前綴指令,它會(huì)鎖定這塊區(qū)域內(nèi)的緩存(緩存行鎖定)并會(huì)回寫(xiě)到主內(nèi)存暖夭。
IA-32架構(gòu)開(kāi)發(fā)者對(duì)lock指令的解釋?zhuān)?br>
1)會(huì)將當(dāng)前處理器緩存行的數(shù)據(jù)立即寫(xiě)會(huì)系統(tǒng)主存
2)這個(gè)寫(xiě)回內(nèi)存的操作會(huì)引起在其他cpu里緩存了該內(nèi)存地址的數(shù)據(jù)無(wú)效(MES協(xié)議)
線程2將initFlag的值store到主內(nèi)存時(shí)要通過(guò)總線锹杈,cpu總線嗅探機(jī)制監(jiān)聽(tīng)到initFlag值被修改,線程1的initFlag失效迈着,線程1需要重新read initFlag的值竭望。
volatile為什么不能保證原子性?
例如你讓一個(gè)volatile的integer自增(i++),其實(shí)要分成3步:
1)讀取volatile變量值到local寥假;
2)增加變量的值市框;
3)把local的值寫(xiě)回,讓其它的線程可見(jiàn)糕韧。這3步的jvm指令為:
mov 0xc(%r10),%r8d ; Load
inc %r8d ; Increment
mov %r8d,0xc(%r10) ; Store
lock addl $0x0,(%rsp) ; StoreLoad Barrier
從Load到store到內(nèi)存屏障枫振,一共4步,其中最后一步j(luò)vm讓這個(gè)最新的變量的值在所有線程可見(jiàn)萤彩,也就是最后一步讓所有的CPU內(nèi)核都獲得了最新的值粪滤,但中間的幾步(從Load到Store)是不安全的,中間如果其他的CPU修改了值將會(huì)丟失
volatile如何實(shí)現(xiàn)禁止指令重排/有序性?
為了實(shí)現(xiàn)volatile的內(nèi)存語(yǔ)義雀扶,編譯器在生成字節(jié)碼時(shí)杖小,會(huì)在指令序列中插入“內(nèi)存屏障”來(lái)禁止特定類(lèi)型的處理器重排序。然而愚墓,對(duì)于編譯器來(lái)說(shuō)予权,發(fā)現(xiàn)一個(gè)最優(yōu)布置來(lái)最小化插入屏障的總數(shù)幾乎不可能,為此浪册,Java內(nèi)存模型采取保守策略:
在每個(gè)volatile寫(xiě)操作的前面插入一個(gè)StoreStore屏障扫腺。
在每個(gè)volatile寫(xiě)操作的后面插入一個(gè)StoreLoad屏障。
在每個(gè)volatile讀操作的后面插入一個(gè)LoadLoad屏障村象。
在每個(gè)volatile讀操作的后面插入一個(gè)LoadStore屏障笆环。
內(nèi)存屏障是什么東西呢?
內(nèi)存屏障厚者,又稱(chēng)內(nèi)存柵欄躁劣,是一個(gè) CPU 指令。
在程序運(yùn)行時(shí)库菲,為了提高執(zhí)行性能账忘,編譯器和處理器會(huì)對(duì)指令進(jìn)行重排序,JMM 為了保證在不同的編譯器和 CPU 上有相同的結(jié)果,通過(guò)插入特定類(lèi)型的內(nèi)存屏障來(lái)禁止+ 特定類(lèi)型的編譯器重排序和處理器重排序闪萄,插入一條內(nèi)存屏障會(huì)告訴編譯器和 CPU:不管什么指令都不能和這條 Memory Barrier 指令重排序梧却。
volatile的應(yīng)用場(chǎng)景
1)定期 “發(fā)布” 觀察結(jié)果供程序內(nèi)部使用“苋ィ【例如】假設(shè)有一種環(huán)境傳感器能夠感覺(jué)環(huán)境溫度放航。一個(gè)后臺(tái)線程可能會(huì)每隔幾秒讀取一次該傳感器,并更新包含當(dāng)前文檔的 volatile 變量圆裕。然后广鳍,其他線程可以讀取這個(gè)變量,從而隨時(shí)能夠看到最新的溫度值吓妆。
2)volatile 變量的規(guī)范使用僅僅是使用一個(gè)布爾狀態(tài)標(biāo)志赊时,用于指示發(fā)生了一個(gè)重要的一次性事件,例如完成初始化或請(qǐng)求停機(jī)行拢。
3)如果讀操作遠(yuǎn)遠(yuǎn)超過(guò)寫(xiě)操作祖秒,您可以結(jié)合使用內(nèi)部鎖和 volatile 變量來(lái)減少公共代碼路徑的開(kāi)銷(xiāo)。
如下顯示的線程安全的計(jì)數(shù)器舟奠,使用 synchronized 確保增量操作是原子的竭缝,并使用 volatile 保證當(dāng)前結(jié)果的可見(jiàn)性。如果更新不頻繁的話沼瘫,該方法可實(shí)現(xiàn)更好的性能抬纸,因?yàn)樽x路徑的開(kāi)銷(xiāo)僅僅涉及 volatile 讀操作,這通常要優(yōu)于一個(gè)無(wú)競(jìng)爭(zhēng)的鎖獲取的開(kāi)銷(xiāo)耿戚。
1. @ThreadSafe
2. public class CheesyCounter {
3. // Employs the cheap read-write lock trick
4. // All mutative operations MUST be done with the 'this' lock held
5. @GuardedBy("this") private volatile int value;
7. //讀操作湿故,沒(méi)有synchronized,提高性能
8. public int getValue() {
9. return value;
10. }
12. //寫(xiě)操作膜蛔,必須synchronized坛猪。因?yàn)閤++不是原子操作
13. public synchronized int increment() {
14. return value++;
15. }