單例模式概念并不復雜,其核心在于這個“單”字,全局只有一個,無法創建多余一個的實例;單例模式在實際使用中很常見,是一種簡單卻實用的設計模式
使用場景
什么時候需要用到單例模式呢?我認為主要有兩種情況
- 最好只有一個實例,即某個類在全局中只需要一個實例即可滿足需求,再創建多余的實例既無必要,也浪費資源,如一個全局的日志類,或者Spring管理下的Bean默認也是單例
- 應該只有一個實例,即某個類的實例不應該超過一個,否則有可能會產生問題,如一個數據庫連接池,全局就只應該只有一個
總體來說,當某個類的實體類最好或應該只有一個實例的時候,就應該考慮使用單例模式進行代碼組織
代碼示例
單例模式比較常用的形式有兩種:饑漢模式與懶漢模式。這兩種模式的共同點在于都是通過私有化構造方法來限制實例的創建,它們的主要區別在于實例是預加載還是懶加載,一般來說建議使用懶加載的形式,畢竟有助于節省資源,提升效率;另外還有一種創建單例的形式,大家可能經常會用到,但不一定意識到自己其實寫了個單例,那就是Java的枚舉,首先枚舉沒有課訪問的構造器,另外枚舉是通過公有的靜態final域為每個枚舉常量導出實例的類,這句話是什么意思呢?注意幾個重要的詞:靜態,final,實例的類,其實意思就是說每個枚舉常量都是一個靜態且final的類的實例,我想寫到這里大家應該很容易能理解為什么說枚舉也是單例了,而且是一個final static
的單例
- 饑漢模式(預加載)
public class EagerSingleton {
private static EagerSingleton instance = new EagerSingleton();
private EagerSingleton(){}
public static EagerSingleton getInstance() {
return instance;
}
}
- 懶漢模式DCL版(懶加載)
public class LazySingleton {
private volatile static LazySingleton instance;
private LazySingleton(){}
public static LazySingleton getInstance() {
if(instance == null){
synchronized(LazySingleton.class){
if(instance == null){
instance = new LazySingleton();
}
}
}
return instance;
}
}
在上述代碼示例的懶漢模式中,使用了DCL(Double checked Locking),個人認為這其實是一種不好的形式,假如不在instance實例前增加volatile
聲明,那么將有可能會導致錯誤,即會導致:unsafe publication
,什么意思呢,即在第一次check時,即便instance不是空,但是也有可能此時的instance還未初始化完全,數據是不完整的,或錯的,而使用volatile
在java 5.0之后雖然確實能避免這樣的問題,但是使得代碼形式顯得比較繁瑣,同時DCL的主要目的在于盡量減少同步帶來的性能損耗,殊不知這種優化基本可以忽略不計,所以我建議一般采用下面簡單的寫法即可
- 懶漢模式簡化版(懶加載)
public class RegularSingleton {
private static RegularSingleton instance;
private RegularSingleton(){}
public synchronized static RegularSingleton getInstance() {
if(instance == null){
instance = new RegularSingleton();
}
return instance;
}
}
一個synchronized
關鍵字即可,不需要volatile
,亦不需DCL
- 枚舉型單例
public enum ManKind {
MAN {
@Override
void sad() {
System.out.println("get a sleep");
}
},
WOMAN {
@Override
void sad() {
System.out.println("shopping");
}
};
abstract void sad();
}
應用實例
在JDK源碼中有很多對單例模式的使用,這里列舉幾個大家有興趣可以閱讀一下