1.什么是volatile
volatile是Java的一個關(guān)鍵字,它提供了一種輕量級的同步機制。相比于重量級鎖synchronized,volatile更為輕量級,因為它不會引起線程上下文的切換和調(diào)度。
2. volatile的兩個作用
可以禁止指令的重排序優(yōu)化
提供多線程訪問共享變量的內(nèi)存可見性
3.禁止指令重排
(1)什么是指令重排
指令重排序是JVM為了優(yōu)化指令,提高程序運行效率,在不影響單線程程序執(zhí)行結(jié)果的前提下,盡可能地提高并行度,例如將多條指令并行執(zhí)行或者是調(diào)整指令的執(zhí)行順序。但是在多線程的情況下,指令重排序可能會帶來問題,例如程序執(zhí)行的順序可能會被調(diào)整。在加上volatile關(guān)鍵字之后可以有效解決這個問題。
下面我們舉個例子:
double r=2.1;//(1)double pi=3.14;//(2)double area=pi*r*r;//(3)
在代碼語句的順序為1->2->3,但實際上順序無論是1->2->3還是2->1->3對結(jié)果并無影響,所以在編譯時和運行時可以根據(jù)需要對1、2語句進行重排序。
重排序是指編譯器和處理器為了優(yōu)化程序性能而對指令序列進行排序的一種手段。重排序需要遵守一定規(guī)則:
①不會對存在數(shù)據(jù)依賴關(guān)系的操作進行重排序
②重排序是為了優(yōu)化性能,但是不管怎么重排序,單線程下程序的執(zhí)行結(jié)果不能被改變
(2)指令重排帶來的問題
我們來看看這個基于雙重檢驗的單例模式:
public class Singleton3{private static Singleton3 instance=null;private Singleton3(){}public static Singleton3 getInstance(){if(instance==null){synchronized(Singleton3.class){if(instance==null)instance=new Singleton3();//非原子操作}}return instance;}}
事實上,這個單例模式的實現(xiàn)方式是有問題的,問題在哪呢?問題在于instance=new Singleton3();并不是一個原子操作。
我們可以將其抽象成以下幾條指令:
memory=allocate();//1:分配對象的內(nèi)存空間
ctorInstance(memory);//2:初始化對象
instance=memory;//3:設(shè)置instance指向剛分配的內(nèi)存地址
可以看到,操作2依賴于操作1,但操作3并不依賴于操作2。所以JVM是可以針對它們進行指令的優(yōu)化重排序的,經(jīng)過重排序后如下:
memory=allocate();//1:分配對象的內(nèi)存空間
instance=memory;//3:instance指向剛分配的內(nèi)存地址,此時對象還未初始化
ctorInstance(memory);//2:初始化對象
指令重排之后,instance指向分配好的內(nèi)存放在了前面,而這段內(nèi)存的初始化被排在了后面。在線程A執(zhí)行這段賦值語句,在初始化分配對象之前就已經(jīng)將其賦值給instance引用,恰好另一個線程進入方法判斷instance引用不為null,然后就將其返回使用,導(dǎo)致出錯。
(3)禁止指令重排的原理
volatile關(guān)鍵字提供內(nèi)存屏障的方式來防止指令被重排,編譯器在生成字節(jié)碼文件時,會在指令序列中插入內(nèi)存屏障來禁止特定類型的處理器重排序。
內(nèi)存屏障會確保指令重排序時不會把其后面的指令排到內(nèi)存屏障之前的位置,也不會把前面的指令排到內(nèi)存屏障的后面;即在執(zhí)行到內(nèi)存屏障這句指令時,在它前面的操作已經(jīng)全部完成。
對于上面的基于雙重檢驗的單例模式,我們只需對其稍作修改即可令其正確運行。我們已經(jīng)知道,問題來自于指令重排,那么我們禁止指令重排即可,用volatile關(guān)鍵字修飾instance變量,使得instance在讀、寫操作前后都會插入內(nèi)存屏障,避免重排序。完整代碼如下:
public class Singleton3{private static volatile Singleton3 instance=null;private Singleton3(){}public static Singleton3 getInstance(){if(instance==null){synchronized(Singleton3.class){if(instance==null)instance=new Singleton3();}}return instance;}}
4.保證內(nèi)存可見性
(1)什么是保證內(nèi)存可見性
Java支持多個線程同時訪問一個對象或者對象的成員變量,由于每個線程可以擁有這個變量的拷貝(雖然對象以及成員變量分配的內(nèi)存是在共享內(nèi)存中的,但是每個執(zhí)行的線程還是可以擁有一份拷貝,這樣做的目的是加速程序的執(zhí)行,這是現(xiàn)代多核處理器的一個顯著特性),所以程序在執(zhí)行過程中,一個線程看到的變量并不一定是最新的。volatile告知程序任何對該變量的訪問均需要從共享內(nèi)存中獲取,而對它的改變必須同步刷新回共享內(nèi)存,它能保證所有線程對變量訪問的可見性。
(2)實現(xiàn)的具體細節(jié)
如果對聲明了volatile的變量進行寫操作,JVM就會向處理器發(fā)送一條Lock前綴的指令,將這個變量所在緩存行的數(shù)據(jù)寫回到系統(tǒng)內(nèi)存。
但是,就算寫回到內(nèi)存,如果其他處理器緩存的值還是舊的,再執(zhí)行計算操作就會有問題。所以,在多處理器下,為了保證各個處理器的緩存是一致的,就會實現(xiàn)緩存一致性協(xié)議,每個處理器通過嗅探在總線上傳播的數(shù)據(jù)來檢查自己緩存的值是不是過期了,當(dāng)處理器發(fā)現(xiàn)自己緩存行對應(yīng)的內(nèi)存地址被修改,就會將當(dāng)前處理器的緩存行設(shè)置成無效狀態(tài),當(dāng)處理器對這個數(shù)據(jù)進行修改操作的時候,會重新從系統(tǒng)內(nèi)存中把數(shù)據(jù)讀到處理器緩存里。
具體的說,內(nèi)存可見性也是通過內(nèi)存屏障實現(xiàn)的,它會執(zhí)行下面兩個操作:
強制將對緩存的修改操作立即寫入主存
如果是寫操作,它會導(dǎo)致其他CPU中對應(yīng)的緩存行無效
5總結(jié)
volatile提供了一種輕量級的同步機制,在訪問volatile變量時不會執(zhí)行加鎖操作,因此也就不會使執(zhí)行線程阻塞
volatile只能確??梢娦?,而加鎖機制既可以確??梢娦杂挚梢源_保原子性
volatile屏蔽掉了JVM中必要的代碼優(yōu)化,所以在效率上比較低
相比synchronized,雖然volatile更簡單并且開銷更低,但它的同步性較差,而且其使用也更容易出錯
Java相關(guān)技術(shù)內(nèi)容
Java中Volatile關(guān)鍵字:http://www.bjpowernode.com/tutorial_java_advance/1261.html
Java Volatile關(guān)鍵字使用場景:http://www.bjpowernode.com/tutorial_java_advance/1262.html
以上就是天津卓眾教育java培訓(xùn)機構(gòu)的小編針對“Java中volatile關(guān)鍵字總結(jié)”的內(nèi)容進行的回答,希望對大家有所幫助,如有疑問,請在線咨詢,有專業(yè)老師隨時為你服務(wù)。