1. 程式人生 > >單例模式幾種實現方式

單例模式幾種實現方式

size ron jdk null singleton bsp 還要 一個 fin

1、餓漢式:靜態常量

特點:單例的實例被聲明成static和final變量了,在第一次加載類到內存中時就會初始化,所以會創建實例本身是線程安全的

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

2、懶漢式:線程不安全

特點:使用了懶加載模式,但是卻存在致命的問題。當多個線程並行調用getInstance()的時候,就會創建多個實例,即在多線程下不能正常工作

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

3、懶漢式:線程安全

特點:線程安全,並且解決了多實例的問題,但是它並不高效。因為在任何時候只能有一個線程調用getInstance()方法,但是同步操作只需要在第一次調用時才被需要,即第一次創建單例實例對象時

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

4、懶漢式:靜態內部類

特點:使用JVM本身機制保證了線程安全問題;由於SingleHolder是私有的,除了getInstacne()之外沒有辦法訪問它,因此它是懶漢式的;同時讀取實例的時候不會進行同步,沒有性能缺陷,也不依賴JDK版本

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

5、雙重檢查鎖

特點:是一種使用同步塊加鎖的方法。又稱其為雙重檢查鎖,因為會有兩次檢查instance == null,一次是在同步塊外,一次是在同步快內。為什麽在同步塊內還要檢驗一次,因為可能會有多個線程一起進入同步塊外的if,如果在同步塊內不進行二次檢驗 的話就會生成多個實例了

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

問題:這樣的寫法在很多平臺和優化編譯器上是錯誤的。

原因在於:instance = new Singleton ()並非是原子操作,事實上在JVM中這句話做了三件事:

1.instance = 給新的實體分配內存

2.調用Singleton的構造函數來初始化instance的成員變量

3.將instance對象指向分配的空間(執行完這一步instance就為null)

現在想象一下有線程A和B在調用getInstance,線程A先進入,在執行到步驟1的時候被踢出了cpu。然後線程B進入,B看到的是instance 已經不是null了(內存已經分配),於是它開始放心地使用instance,但這個是錯誤的,因為在這一時刻,instance的成員變量還都是缺省值,A還沒有來得及執行步驟2來完成instance的初始化。

當然編譯器也可以這樣實現:

1. temp = 分配內存

2. 調用temp的構造函數

3. instance = temp

如果編譯器的行為是這樣的話我們似乎就沒有問題了,但事實卻不是那麽簡單,因為我們無法知道某個編譯器具體是怎麽做的,因為在Java的memory model裏對這個問題沒有定義。

雙檢鎖對於基礎類型(比如int)適用。很顯然吧,因為基礎類型沒有調用構造函數這一步。

6、枚舉

特點:通過EasySingleton.INSTANCE來訪問實例,這比調用getInstance()方法簡單多了。創建枚舉默認就是線程安全,而且還能防止反序列化導致重新創建新的對象

public class Singleton {
    public enum EasySingleton{  
        INSTANCE;  
    }  
}

單例模式幾種實現方式