java雙重檢查鎖定與延遲初始化

概述

在Java多線程程序中,有時候需要採用延遲初始化來降低初始化類和創建對象的開銷。雙重檢查鎖是常見的延遲初始化技術,但引入新的問題。

雙重檢查鎖

public class DoubleCheckedLocking {
    private static Instance instance;
    public static Instance getInstance() {
        if (instance == null) {// 第一次檢查
            synchronized (DoubleCheckedLocking.class) {// 加鎖
                if (instance == null)// 第二次檢查
                    instance = new Instance();// 問題根源
            }
        }
    }
}

多線程程序中,讀取到instance不爲null時,instance引用的對象有可能還沒有完成初始化。

問題的根源

instance = new Singleton();這一行代碼可以分解爲3行僞代碼:

memory = allocate();      // 1:分配對象的內存空間
ctorInstance(memory);   // 2:初始化對象
instance = memory;        // 3:設置instance指向剛分配的內存地址

上面3行僞代碼中的2和3之間,可能會被重排序(在一些JIT編譯器上,這種重排序是真實發生的)

memory = allocate();      // 1:分配對象的內存空間
instance = memory;        // 3:設置instance指向剛分配的內存地址
// 此時對象還沒有被初始化
ctorInstance(memory);   // 2:初始化對象

基於volatile的解決方案

將instance聲明爲volatile類型,就可以實現線程安全的延遲初始化。
volatile禁止指令重排序。

基於類初始化的解決方案

JVM在類的初始化階段(Class被加載後,且被線程使用之前),會執行類的初始化。執行類的初始化期間,JVM會去獲取一個鎖。這個鎖可以同步多個線程對同一個類的初始化。

public class InstanceFactory {
    private static class InstanceHolder {
        public static Instance instance = new Instance();
    }

	public static Instance getInstance() {
	    return InstanceHolder.instance;
	}
}

重排序對其他線程不可見。
Java語言規範規定,對於每一個類或接口C,都有一個唯一的初始化鎖LC與之對應。

參考

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