單例模式(Singleton)是創建者模式的一種,是負責創建對象的模式。
模式解決的問題
該模式的核心是保證一個類只有一個實例,並且提供一個訪問該實例的全局訪問點。
模式的優點
由於單例模式只生成一個單例,減少了系統性能的開銷,當一個對象的產生需要較多的資源時,例如讀取配置、產生其他依賴對象時,則可以通過在應用啓動時產生一個單例對象,然後永久駐留在內存的方式解決。
單例模式可以在系統設置全局的訪問點,優化環共享資源訪問,例如可以設置一個單例類,負責所有數據表的映射處理。
模式的缺點
一、開銷
雖然數量很少,但如果每次對象請求引用時都要檢查是否存在類的實例,將仍然需要一些開銷。可以通過使用靜態初始化解決此問題。
二、可能的開發混淆
使用單例對象(尤其在類庫中定義的對象)時,開發人員必須記住自己不能使用new關鍵字實例化對象。因爲可能無法訪問庫源代碼,因此應用程序開發人員可能會意外發現自己無法直接實例化此類。
三、對象生存期
不能解決刪除單個對象的問題。在提供內存管理的語言中,只有單例類能夠導致實例被取消分配,因爲它包含對該實例的私有引用。
模式的實現方式
- 餓漢式(線程安全,調用率高,但是,不可以延時加載)
餓漢式是在對象創建的時候就直接賦值。
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();
單例的破解
- 單例模式可以用反射模式來調用它私有化的構造方法來破解。
反射機制得到類的對象之後可以通過setAccessible(true),這樣就可以調用私有化構造方法。這種破解方式不能破解枚舉方式的單例。如果想要保護方法不被破解,就要在私有化的構造方法中過濾條件,如果沒有單例對象卻調用構造方法,就報錯,就可以不被這種方法破解。 - 單例模式也可以通過序列化和反序列化的方式破解。
通過反序列化時的readObject()方法來獲取單例對象,但是這個破解方法需要單例類實現Serializable接口。保護類不被這個方法破解也很簡單,可以在類中重寫一個私有化的readObject()方法,使其返回單例對象。