設計模式之單例模式

1、概念

JVM中,單例對象只有一個實例存在。java

2、餓漢式實現

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

最簡單的實現方式,可是若是對象的構造耗費時間,可能採用懶漢式更好。安全

3、懶漢式實現一

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

也是很簡單粗暴的懶漢式實現方式,每次獲取單例的時候都須要獲取排他鎖,效率差。多線程

4、懶漢式實現二

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;
    }
}

一、雙重判斷

  • 第一次判斷是防止實例化完畢後的無效同步處理
  • 若是沒有第二次校驗,假設線程t1執行了第一次校驗後,判斷爲null,這時t2也獲取了CPU執行權,也執行了第一次校驗,判斷也爲null。接下來t2得到鎖,建立實例。這時t1又得到CPU執行權,因爲以前已經進行了第一次校驗,結果爲null(不會再次判斷),得到鎖後,直接建立實例。結果就會致使建立多個實例。因此須要在同步代碼裏面進行第二次校驗,若是實例爲空,則進行建立。

二、指令重排

指令重排序是JVM爲了優化指令,提升程序運行效率,在不影響單線程程序執行結果的前提下,儘量地提升並行度。
也就是說,JVM爲了執行效率會將指令進行從新排序,可是這種從新排序不會對單線程程序產生影響。優化

因爲instance = new Singleton();操做並非一個原子性指令,會被分爲多個指令:ui

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

可是通過重排序後以下:.net

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

三、可見性

instance須要加volatile關鍵字,不然會出現錯誤。問題的緣由在於JVM指令重排優化的存在。在某個線程建立單例對象時,在構造方法被調用以前,就爲該對象分配了內存空間(空白內存)並將分配的內存地址賦值給instance字段了,然而該對象可能尚未初始化。若緊接着另一個線程來調用getInstance,取到的就是狀態不正確的對象,程序就會出錯。線程

5、懶漢式實現三

public class Singleton {
    private static class SingletonHolder {
        private static Singleton instance = new Singleton();

        private SingletonHolder(){
        }
    }

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

一、加載時機

內部靜態類是要在有引用了之後纔會裝載到內存的,因此在你第一次調用getInstance()以前,SingletonHolder是沒有被裝載進來的,只有在你第一次調用了getInstance()以後,裏面涉及到了return SingletonHolder.instance; 產生了對SingletonHolder的引用,內部靜態類的實例纔會真正裝載。這也就是懶加載的意思。code

遇到new、getstatic、putstatic、或invokestatic這4條字節碼指令時,若是類沒有進行過初始化,則須要先觸發其初始化,生成這4條指令的最多見的Java代碼場景是:使用new關鍵字實例化對象的時候,讀取或設置一個類的靜態字段(被final修飾、已在編譯期把結果放入常量池的靜態字段除外)的時候,以及調用一個類的靜態方法的時候。對象

二、線程安全

虛擬機會保證一個類的 ()方法在多線程環境中被正確的加鎖、同步,若是多個線程同時去初始化一個類,那麼只會有一個線程去執行這個類的 ()方法,其餘線程都須要阻塞等待,只到活動線程執行 ()方法完畢。 blog

相關文章
相關標籤/搜索