設計模式GOF23——單例模式

單例模式(Singleton)是創建者模式的一種,是負責創建對象的模式。

模式解決的問題

該模式的核心是保證一個類只有一個實例,並且提供一個訪問該實例的全局訪問點。

模式的優點

由於單例模式只生成一個單例,減少了系統性能的開銷,當一個對象的產生需要較多的資源時,例如讀取配置、產生其他依賴對象時,則可以通過在應用啓動時產生一個單例對象,然後永久駐留在內存的方式解決。
單例模式可以在系統設置全局的訪問點,優化環共享資源訪問,例如可以設置一個單例類,負責所有數據表的映射處理。

模式的缺點

一、開銷
雖然數量很少,但如果每次對象請求引用時都要檢查是否存在類的實例,將仍然需要一些開銷。可以通過使用靜態初始化解決此問題。
二、可能的開發混淆
使用單例對象(尤其在類庫中定義的對象)時,開發人員必須記住自己不能使用new關鍵字實例化對象。因爲可能無法訪問庫源代碼,因此應用程序開發人員可能會意外發現自己無法直接實例化此類。
三、對象生存期
不能解決刪除單個對象的問題。在提供內存管理的語言中,只有單例類能夠導致實例被取消分配,因爲它包含對該實例的私有引用。

模式的實現方式

  1. 餓漢式(線程安全,調用率高,但是,不可以延時加載)
    餓漢式是在對象創建的時候就直接賦值。
public class Singleton {
    //單例的對象在創建的時候初始化,private保證外部不能隨便訪問,static讓外部類可以通過getInstance()方法訪問
    private static Singleton singleton = new Singleton();
    //把構造方法私有化,防止外部創建類對象
    private Singleton(){}
    //對外設定的可以訪問類的方法
    public static Singleton getInstance(){
        return singleton;
    }
}

調用的時候也很簡單
Singleton s = Singleton.getInstance();
就可以了。
2. 懶漢式(線程安全,調用率高,但是,可以延時加載)
懶漢式是在調用的時候再創建對象

public class Singleton {
    //private保證外部不能隨便訪問,static讓外部類可以通過getInstance()方法訪問
    private static Singleton singleton = null;
    //把構造方法私有化,防止外部創建類對象
    private Singleton(){}
    //對外設定的可以訪問類的方法,沒有對象的話創建對象,synchronized保證線程安全
    public static synchronized Singleton getInstance(){
        if(singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

調用的時候也很簡單
Singleton s = Singleton.getInstance();
3. 雙重檢索式(由於JVM底層內部模型原因,偶爾會出問題)
雙重檢索式是用雙重鎖的方式保證創建對象的線程安羣問題

public class Singleton {
    //private保證外部不能隨便訪問,static讓外部類可以通過getInstance()方法訪問
    private static Singleton singleton = null;
    //把構造方法私有化,防止外部創建類對象
    private Singleton(){}
    //對外設定的可以訪問類的方法,沒有對象的話創建對象,synchronized保證線程安全
    //synchronized放在方法裏面會減少線程衝突的可能性,不用每次都同步,提高效率
    public static  Singleton getInstance(){
        if (singleton == null) {  
            synchronized (Singleton.class) {  
            if (singleton == null) {  
                singleton = new Singleton();  
            }  
            }  
        }  
        return singleton; 
    }
}

調用的時候也很簡單
Singleton s = Singleton.getInstance();
4. 靜態內部類(線程安全,調用率高,但是,可以延時加載)
靜態內部類是用靜態內部類的方式創建對象

public class Singleton {
    //靜態內部類創建對象,因爲是靜態內部類,又是私有類,所以只有本類可以訪問
     private static class SingletonHolder {  
            private static final Singleton INSTANCE = new Singleton();  
    }  
    //把構造方法私有化,防止外部創建類對象
    private Singleton(){}
    //對外設定的可以訪問類的方法,訪問靜態內部類的對象
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE;  
    }  
}

調用的時候也很簡單
Singleton s = Singleton.getInstance();
5. 枚舉單例(線程安全,調用率高,不能延時加載)
用枚舉類來保存對象,枚舉類本身就是天然單例模式。

public enum Singleton {  
    INSTANCE;  
}

調用的時候也很簡單
Singleton s = Singleton.INSTANCE;
6. 靜態塊(線程安全,調用率高,不能延時加載)
用靜態塊的方式創建對象。

public class Singleton {
    //private保證外部不能隨便訪問,static讓外部類可以通過getInstance()方法訪問
    private static Singleton singleton = null;
    //單例的對象在靜態塊初始化
    static{
        singleton = new Singleton();
    }
    //把構造方法私有化,防止外部創建類對象
    private Singleton(){}
    //對外設定的可以訪問類的方法
    public static Singleton getInstance(){
        return singleton;
    }
}

調用的時候也很簡單
Singleton s = Singleton.getInstance();

單例的破解

  1. 單例模式可以用反射模式來調用它私有化的構造方法來破解。
    反射機制得到類的對象之後可以通過setAccessible(true),這樣就可以調用私有化構造方法。這種破解方式不能破解枚舉方式的單例。如果想要保護方法不被破解,就要在私有化的構造方法中過濾條件,如果沒有單例對象卻調用構造方法,就報錯,就可以不被這種方法破解。
  2. 單例模式也可以通過序列化和反序列化的方式破解。
    通過反序列化時的readObject()方法來獲取單例對象,但是這個破解方法需要單例類實現Serializable接口。保護類不被這個方法破解也很簡單,可以在類中重寫一個私有化的readObject()方法,使其返回單例對象。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章