volatile關(guān)鍵字

Java 內(nèi)存模型中的可見性、原子性和有序性。

可見性:可見性,是指線程之間的可見性,一個(gè)線程修改的狀態(tài)對(duì)另一個(gè)線程是可見的。
原子性:原子是世界上的最小單位,具有不可分割性。比如 a=0;(a非long和double類型) 這個(gè)操作是不可分割的,那么我們說這個(gè)操作時(shí)原子操作。再比如:a++; 這個(gè)操作實(shí)際是a = a + 1;是可分割的,所以他不是一個(gè)原子操作。非原子操作都會(huì)存在線程安全問題,需要我們使用同步技術(shù)(sychronized)來讓它變成一個(gè)原子操作。
有序性:Java 語言提供了 volatile 和 synchronized 兩個(gè)關(guān)鍵字來保證線程之間操作的有序性,volatile 是因?yàn)槠浔旧戆敖怪噶钪嘏判颉钡恼Z義,synchronized 是由“一個(gè)變量在同一個(gè)時(shí)刻只允許一條線程對(duì)其進(jìn)行 lock 操作”這條規(guī)則獲得的,此規(guī)則決定了持有同一個(gè)對(duì)象鎖的兩個(gè)同步塊只能串行執(zhí)行。

volatile的工作流程
image.png
volatile的底層原理

voliate緩存可見性實(shí)現(xiàn)原理:

底層實(shí)現(xiàn)主要是通過匯編lock前綴指令,它會(huì)鎖定這塊區(qū)域內(nèi)的緩存(緩存行鎖定)并會(huì)回寫到主內(nèi)存。

IA-32架構(gòu)開發(fā)者對(duì)lock指令的解釋:
1)會(huì)將當(dāng)前處理器緩存行的數(shù)據(jù)立即寫會(huì)系統(tǒng)主存
2)這個(gè)寫回內(nèi)存的操作會(huì)引起在其他cpu里緩存了該內(nèi)存地址的數(shù)據(jù)無效(MES協(xié)議)

線程2將initFlag的值store到主內(nèi)存時(shí)要通過總線,cpu總線嗅探機(jī)制監(jiān)聽到initFlag值被修改,線程1的initFlag失效,線程1需要重新read initFlag的值。


image.png
volatile為什么不能保證原子性?

例如你讓一個(gè)volatile的integer自增(i++),其實(shí)要分成3步:
1)讀取volatile變量值到local;
2)增加變量的值;
3)把local的值寫回,讓其它的線程可見。這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è)最新的變量的值在所有線程可見,也就是最后一步讓所有的CPU內(nèi)核都獲得了最新的值,但中間的幾步(從Load到Store)是不安全的,中間如果其他的CPU修改了值將會(huì)丟失

volatile如何實(shí)現(xiàn)禁止指令重排/有序性?

為了實(shí)現(xiàn)volatile的內(nèi)存語義,編譯器在生成字節(jié)碼時(shí),會(huì)在指令序列中插入“內(nèi)存屏障”來禁止特定類型的處理器重排序。然而,對(duì)于編譯器來說,發(fā)現(xiàn)一個(gè)最優(yōu)布置來最小化插入屏障的總數(shù)幾乎不可能,為此,Java內(nèi)存模型采取保守策略:
在每個(gè)volatile寫操作的前面插入一個(gè)StoreStore屏障。
在每個(gè)volatile寫操作的后面插入一個(gè)StoreLoad屏障。
在每個(gè)volatile讀操作的后面插入一個(gè)LoadLoad屏障。
在每個(gè)volatile讀操作的后面插入一個(gè)LoadStore屏障。

內(nèi)存屏障是什么東西呢?

內(nèi)存屏障,又稱內(nèi)存柵欄,是一個(gè) CPU 指令。
在程序運(yùn)行時(shí),為了提高執(zhí)行性能,編譯器和處理器會(huì)對(duì)指令進(jìn)行重排序,JMM 為了保證在不同的編譯器和 CPU 上有相同的結(jié)果,通過插入特定類型的內(nèi)存屏障來禁止+ 特定類型的編譯器重排序和處理器重排序,插入一條內(nèi)存屏障會(huì)告訴編譯器和 CPU:不管什么指令都不能和這條 Memory Barrier 指令重排序。

volatile的應(yīng)用場(chǎng)景

1)定期 “發(fā)布” 觀察結(jié)果供程序內(nèi)部使用?!纠纭考僭O(shè)有一種環(huán)境傳感器能夠感覺環(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)超過寫操作,您可以結(jié)合使用內(nèi)部鎖和 volatile 變量來減少公共代碼路徑的開銷。

如下顯示的線程安全的計(jì)數(shù)器,使用 synchronized 確保增量操作是原子的,并使用 volatile 保證當(dāng)前結(jié)果的可見性。如果更新不頻繁的話,該方法可實(shí)現(xiàn)更好的性能,因?yàn)樽x路徑的開銷僅僅涉及 volatile 讀操作,這通常要優(yōu)于一個(gè)無競(jìng)爭(zhēng)的鎖獲取的開銷。

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.  //讀操作,沒有synchronized,提高性能  
8.  public int getValue() {   
9.  return value;   
10.  }   

12.  //寫操作,必須synchronized。因?yàn)閤++不是原子操作  
13.  public synchronized int increment() {  
14.  return value++;  
15.  }  
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容