1. 程式人生 > >為什麼在單例類中不能使用雙重檢查鎖來初始化物件

為什麼在單例類中不能使用雙重檢查鎖來初始化物件

在網上看到過好多篇文章在說明雙重檢查鎖在多個執行緒初始化一個單例類時到底為什麼不行時在關鍵位置的描述模稜兩可,今天我們就來看一下為什麼不能用雙重檢查鎖,問題到底出在了那裡?

下面我們直接進入主題,為什麼使用雙重檢查鎖,原因是因為在多執行緒初始化一個單例類時我們要確保得到一個物件,又想再確保一個物件時得到更高的效率,所以就有了雙重檢查鎖,使用雙重檢查鎖初始化物件的程式碼如下

public class DoubleCheckedLocking {                 //1
    private static DoubleCheckedLocking instance;                   //2

    public static DoubleCheckedLocking getInstance() {              //3
        if (instance == null) {                                     //4:第一次檢查
            synchronized (DoubleCheckedLocking.class) {             //5:加鎖
                if (instance == null)                               //6:第二次檢查
                    instance = new DoubleCheckedLocking();          //7:問題的根源出在這裡
            }                                                       //8
        }                                                           //9
        return instance;                                            //10
    }                                                               //11
}    
為什麼這樣是不行的,問題的根源出在第7行(instance = new DoubleCheckedLocking();),建立一個物件可以分解為如下三步:
memory = allocate();   //1:分配物件的記憶體空間
ctorInstance(memory);  //2:初始化物件
instance = memory;     //3:設定instance指向剛分配的記憶體地址
上面三行虛擬碼中的2和3之間,可能會被重排序,2和3之間重排序之後的執行時序如下:
memory = allocate();   //1:分配物件的記憶體空間
instance = memory;     //3:設定instance指向剛分配的記憶體地址
                       //注意,此時物件還沒有被初始化!
ctorInstance(memory);  //2:初始化物件
重排序不能影響單執行緒的執行語義,雖然這裡2和3進行了重排序,但是隻要保證2排在4前面執行,單執行緒內的執行結果不會被改變
時間 執行緒A 執行緒B
t1 A1:分配物件的記憶體空間
t2 A3:設定instance指向記憶體空間
t3 B1:判斷instance是否為空
t4 B2:由於instance不為null,執行緒B將訪問instance引用的物件(而這個時候物件還沒有初始化)
t5 A2:初始化物件
t6 A4:訪問instance引用的物件
執行緒B拿到一個未初始化的物件去操作,結果肯定就出錯了

總結,到此為止我們只說明瞭為什麼不可以用雙重檢查鎖來初始化物件

THE  END!!!