Java重排序

之前聽公司講座說到的設計模式,經典的懶漢式單例模式會有重排序問題,當時不是很理解,後來深入學了JVM終於恍然大悟,這裏做個總結分享。
重排序排序的就是操作指令的順序,改變了指令的執行順序。重排序首先要知道字節碼.class文件,它就是JavaC編譯後的那個字節碼文件,它裏面有操作指令的執行順序,程序計數器就是根據字節碼的操作指令的順序進行尋址查找屬性和方法進行操作。JVM會自行判斷,把速度快的邏輯簡單的代碼先執行。
但是有的情況下,JVM會自己對執行指令進行一些優化,比如我們知道for循環重複讀取一個值,它會直接從緩存中讀取,這時候值就算髮生改變JVM也沒及時發現。就要加volatile關鍵字修飾那個屬性值,volatile關鍵字簡單來說,就是不讓JVM進行優化。
重排序問題也是JVM做的優化,但是變成了好心做壞事。
比如單例模式下,我們都知道創建對象過程中,分配空間是在加載階段,而裏面有什麼屬性什麼方法,都是初始化階段纔有的。JVM優化後就可能直接return了一個全部是null的只是分配了內存空間但是沒有初始化的對象。
來看簡單的懶漢式單例模式例子:
public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static synchronized Singleton getInstance() {  // (1)線程A進來了
    if (instance == null) {               // (3)然後再執行new Singleton
        instance = new Singleton();      
    }  
    return instance;  // (2)JVM優化重排序,直接先返回了instance對象
    }  
}
比如有個方法A先執行,再給屬性B附值,最後B return,但是A執行復雜,JVM覺得A可能不影響B的return,就先附值return B,再慢慢跑A方法,這就是操作指令的執行順序變了(字節碼文件可以看到的)。
所以,懶漢式單例模式必須加入雙重鎖校驗和volatile關鍵字,使得 == null 判斷每次都會去讀取最新的singleton,而不是讀緩存中的那個,這就解決了JVM的重排序問題。
public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {           // 第一次檢查對象是否爲空
        synchronized (Singleton.class) {  
        if (singleton == null) {      // 第二次檢查對象是否爲空
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;                 // 最後才能return
    }  
}
具體的測試代碼在網上也很多,都可以證明JVM的重排序是真實存在的,而且一發生了問題就很難排查,除非不怕辛苦一行行看字節碼操作指令的一行行執行。

鞠躬。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章