提到單例模式,相信都不會陌生,今天對其進行總結。
以下是單例模式的特點:
- 單例類只能有一個實例。
- 單例類必須自己自己創建自己的唯一實例。
- 單例類必須給所有其他對象提供這一實例。
種類的話不好說有幾類,因為要考慮到是否在多線程下運行,下面來介紹主要的幾類:
懶漢類
//懶漢式單例類.在第一次調用的時候實例化自己
public class Singleton {
private Singleton() {
}
private static Singleton single = null;
//靜態工廠方法
public static Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}
懶漢么,所以在多線程下會失效,所以下面介紹三種懶漢的升級版來適應多線程
- 在getinstance前加上synchronized(同步),但這導致的是每次getInstance都會去同步,消耗資源。
public class Singleton {
private Singleton() {
}
private static Singleton single = null;
// 靜態工廠方法
public static synchronized Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}
- 雙重檢查鎖,它是在以上做的修改,判斷兩次空,所以只有在第一次調用的時候會同步,避免了每次同步資源的消耗,注意
volatile
關鍵字。
public class Singleton {
private Singleton() {
}
private volatile static Singleton singleton = null; // 聲明成 volatile
//靜態工廠方法
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
- 內部靜態類,這種我覺得是最好的,既實現了線程安全,也避免了同步帶來的性能影響。
public class Singleton {
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}
餓漢類
餓漢式是典型的空間換時間,當類裝載的時候就會創建類的實例,不管你用不用,先創建出來,然后每次調用的時候,就不需要再判斷,節省了運行時間。
//餓漢式單例類.在類初始化時,已經自行實例化
public class Singleton {
private Singleton() {
}
private static final Singleton single = new Singleton();
//靜態工廠方法
public static Singleton getInstance() {
return single;
}
}
這種也是我比較喜歡的,因為簡單易懂,但當實現了Serializable接口后,反序列化時單例會被破壞,實現Serializable接口需要重寫readResolve,才能保證其反序列化依舊是單例:
private Object readResolve() throws ObjectStreamException {
return single;
}
枚舉類
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
這種方式是Effective Java作者Josh Bloch 提倡的方式,它不僅能避免多線程同步問題,而且還能防止反序列化重新創建新的對象,可謂是很堅強的壁壘啊,不過,個人認為由于1.5中才加入enum特性,用這種方式寫不免讓人感覺生疏,在實際工作中,我也很少看見有人這么寫過。
以上就是常用的單例模式,一般的情況下,我會使用餓漢式,只有在要明確實現lazy loading效果時才會使用內部靜態類,另外,如果涉及到反序列化創建對象時我會試著使用枚舉的方式來實現單例,不過,我一直會保證我的程序是線程安全的。