1. 程式人生 > >Java中的volatile關鍵字

Java中的volatile關鍵字

加載機制 新的 避免 tile 指令重排序 code ret pre ()

volatile關鍵字的作用是保證多線程執行的同步性。

在java虛擬機的內存模型中,有主內存和工作內存的概念,每個線程對應一個工作內存,並共享主內存的數據,下面看看操作普通變量和volatile變量有什麽不同:

1、對於普通變量:讀操作會優先讀取工作內存的數據,如果工作內存中不存在,則從主內存中拷貝一份數據到工作內存中;寫操作只會修改工作內存的副本數據,這種情況下,其它線程就無法讀取變量的最新值。

2、對於volatile變量,讀操作時JMM會把工作內存中對應的值設為無效,要求線程從主內存中讀取數據;寫操作時JMM會把工作內存中對應的數據刷新到主內存中,這種情況下,其它線程就可以讀取變量的最新值。

volatile變量的內存可見性是基於內存屏障(Memory Barrier)實現的,什麽是內存屏障?內存屏障,又稱內存柵欄,是一個CPU指令。在程序運行時,為了提高執行性能,編譯器和處理器會對指令進行重排序,JMM為了保證在不同的編譯器和CPU上有相同的結果,通過插入特定類型的內存屏障來禁止特定類型的編譯器重排序和處理器重排序,插入一條內存屏障會告訴編譯器和CPU:不管什麽指令都不能和這條Memory Barrier指令重排序。

CPU為了提高處理性能,並不直接和內存進行通信,而是將內存的數據讀取到內部緩存(L1,L2)再進行操作,但操作完並不能確定何時寫回到內存,如果對volatile變量進行寫操作,當CPU執行到Lock前綴指令時,會將這個變量所在緩存行的數據寫回到內存,不過還是存在一個問題,就算內存的數據是最新的,其它CPU緩存的還是舊值,所以為了保證各個CPU的緩存一致性,每個CPU通過嗅探在總線上傳播的數據來檢查自己緩存的數據有效性,當發現自己緩存行對應的內存地址的數據被修改,就會將該緩存行設置成無效狀態,當CPU讀取該變量時,發現所在的緩存行被設置為無效,就會重新從內存中讀取數據到緩存中。

程序運行時需要CPU、高速緩存、內存三個部件協同工作,線程A把內存中的變量x讀到了高速緩存,線程B現在修改了變量x。內存中的x雖然變了,但是線程A依舊認為高速緩存中的x是最新的。這就影響了線程之間的同步。
volatile關鍵字的作用就是:如果線程B修改了變量x,那麽線程A的高緩中的x就失效了,需要從內存中重新加載。

單例模式中使用volatile更穩妥,這種單例實現方式叫做“double check”,在進入synchronized區域之前檢查一次,進入之後再檢查一次。外層的if能夠避免所有線程都進入synchronized區域,從而避免了線程同時等待一個鎖,內層的if用於準確判斷,免得其它線程已經創建了單例。

class Singleton {
    private volatile static Singleton instance;
    public static Singleton getInstance() {
        if (instance == null) {
            syschronized(Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    } 
}

另外一種依賴類加載機制的單例模式實現方法

public class Singleton {  
    static class SingletonHolder {  
        static Singleton instance = new Singleton();  
    }  
      
    public static Singleton getInstance(){  
        return SingletonHolder.instance;  
    }  
}

參考資料

https://www.jianshu.com/p/195ae7c77afe
http://www.importnew.com/27863.html#comment-638620

Java中的volatile關鍵字