volatile 關鍵字 (一)
文章目錄
- volatile 關鍵字 (一)
- 如何保證變量的可見性?
- 如何禁止指令重排序?
文章來自Java Guide 用于學習如有侵權,立即刪除
如何保證變量的可見性?
在 Java 中,volatile
關鍵字可以保證變量的可見性,如果我們將變量聲明為 volatile
,這就指示 JVM,這個變量是共享且不穩定的,每次使用它都到主存中進行讀取。
volatile
關鍵字其實并非是 Java 語言特有的,在 C 語言里也有,它最原始的意義就是禁用 CPU 緩存。如果我們將一個變量使用 volatile
修飾,這就指示 編譯器,這個變量是共享且不穩定的,每次使用它都到主存中進行讀取。
volatile
關鍵字能保證數據的可見性,但不能保證數據的原子性。synchronized
關鍵字兩者都能保證。
如何禁止指令重排序?
在 Java 中,volatile
關鍵字除了可以保證變量的可見性,還有一個重要的作用就是防止 JVM 的指令重排序。 如果我們將變量聲明為 volatile
,在對這個變量進行讀寫操作的時候,會通過插入特定的 內存屏障 的方式來禁止指令重排序。
在 Java 中,Unsafe
類提供了三個開箱即用的內存屏障相關的方法,屏蔽了操作系統底層的差異:
public native void loadFence();
public native void storeFence();
public native void fullFence();
理論上來說,你通過這個三個方法也可以實現和volatile
禁止重排序一樣的效果,只是會麻煩一些。
下面我以一個常見的面試題為例講解一下 volatile
關鍵字禁止指令重排序的效果。
面試中面試官經常會說:“單例模式了解嗎?來給我手寫一下!給我解釋一下雙重檢驗鎖方式實現單例模式的原理唄!”
雙重校驗鎖實現對象單例(線程安全):
public class Singleton {private volatile static Singleton uniqueInstance;private Singleton() {}public static Singleton getUniqueInstance() {//先判斷對象是否已經實例過,沒有實例化過才進入加鎖代碼if (uniqueInstance == null) {//類對象加鎖synchronized (Singleton.class) {if (uniqueInstance == null) {uniqueInstance = new Singleton();}}}return uniqueInstance;}
}
uniqueInstance
采用 volatile
關鍵字修飾也是很有必要的, uniqueInstance = new Singleton();
這段代碼其實是分為三步執行:
- 為
uniqueInstance
分配內存空間 - 初始化
uniqueInstance
- 將
uniqueInstance
指向分配的內存地址
但是由于 JVM 具有指令重排的特性,執行順序有可能變成 1->3->2。指令重排在單線程環境下不會出現問題,但是在多線程環境下會導致一個線程獲得還沒有初始化的實例。例如,線程 T1 執行了 1 和 3,此時 T2 調用 getUniqueInstance
() 后發現 uniqueInstance
不為空,因此返回 uniqueInstance
,但此時 uniqueInstance
還未被初始化。
大家好,我是xwhking,一名技術愛好者,目前正在全力學習 Java,前端也會一點,如果你有任何疑問請你評論,或者可以加我QQ(2837468248)說明來意!希望能夠與你共同進步