The "Double-Checked Locking is Broken" Declaration

雙重檢查鎖定在延遲初始化的單例模式中見得比較多(單例模式實現方式不少,這裏爲說明雙重檢查鎖定問題,只選取這一種方式),先來看一個版本:多線程

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

上面是最原始的模式,一眼就能夠看出,在多線程環境下,可能會產生多個Singleton實例,因而有了其同步的版本:線程

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

在這個版本中,每次調用getInstance都須要取得Singleton.class上的鎖,然而該鎖只是在開始構建Singleton 對象的時候纔是必要的,後續的多線程訪問,效率會下降,因而有了接下來的版本:code

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

很好的想法!不幸的是,該方案也未能解決問題之根本:
緣由在於:初始化Singleton 和 將對象地址寫到instance字段 的順序是不肯定的。在某個線程new Singleton()時,在構造方法被調用以前,就爲該對象分配了內存空間並將對象的字段設置爲默認值。
此時就能夠將分配的內存地址賦值給instance字段了,然而該對象可能尚未初始化;此時若另一個線程來調用getInstance,取到的就是狀態不正確的對象。
鑑於以上緣由,有人可能提出下列解決方案:對象

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

該方案將Singleton對象的構造置於最裏面的同步塊,這種思想是在退出該同步塊時設置一個內存屏障,以阻止初始化Singleton 和 將對象地址寫到instance字段 的從新排序。
不幸的是,這種想法也是錯誤的,同步的規則不是這樣的。退出監視器(退出同步)的規則是:因此在退出監視器前面的動做都必須在釋放監視器以前完成。然而,並無規定說退出監視器以後的動做不能放到退出監視器以前完成。也就是說同步塊裏的代碼必須在退出同步時完成,而同步塊後面的代碼則能夠被編譯器或運行時環境移到同步塊中執行。
編譯器能夠合法的,也是合理的,將instance = temp移動到最裏層的同步塊內,這樣就出現了上個版本一樣的問題。
在JDK1.5及其後續版本中,擴充了volatile語義,系統將不容許對 寫入一個volatile變量的操做與其以前的任何讀寫操做 從新排序,也不容許將 讀取一個volatile變量的操做與其以後的任何讀寫操做 從新排序。
在jdk1.5及其後的版本中,能夠將instance 設置成volatile以讓雙重檢查鎖定生效,以下:排序

public class Singleton {
    private static volatile Singleton instance = null;
    private Singleton(){}
    public static Singleton getInstance() {
       if(instance == null) {
           synchronized(Singleton.class) {
              if(instance == null) {
                  instance = new Singleton();
              }
           }
       }
       return instance;
    }
}
相關文章
相關標籤/搜索