為什么要使用單例模式
單例的優點
1.只有一個實例,節省開銷
2.全局使用方便,同時避免頻繁創建和銷毀
使用單例的注意點:
要避免造成 內存泄漏
常用的單例模式
單例不僅要滿足線程安全,還要注意防止序列化產生新對象。如果單例實現了Serializable接口,就必須加入如下方法(枚舉單例不用這么做,因為JVM能保障這點):
private Object readResolve() throws ObjectStreamException{
return INSTANCE;
}
餓漢式
餓漢式:就是在類初始化時就實例化,所以是線程安全的。
缺點是:
1.沒有懶加載,在不需要的時候也會被實例化,造成內存浪費。
2.實例化方法對外部調用不友好,傳參不方便
public class Singleton implements Serializable {
private static final Singleton INSTANCE = new Singleton();
// 私有化構造函數
private Singleton(){}
public static Singleton getInstance(){
return INSTANCE;
}
/**
* 如果實現了Serializable, 必須重寫這個方法
*/
private Object readResolve() throws ObjectStreamException {
return INSTANCE;
}
}
懶漢式
延遲加載(使用時加載),節省內存
雙重判空,第一次判空防止重復加鎖,第二次判空才實例化
防止DCL指令重拍序,加volatile關鍵字
public class Singleton {
private volatile static Singleton INSTANCE; //聲明成 volatile
private Singleton (){}
public static Singleton getSingleton() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
//如果實現了Serializable, 必須重寫同上面餓漢式一樣的readResolve方法
枚舉式
由JVM保證線程安全
序列化和反射攻擊已經被枚舉解決
//enum枚舉類
public enum Singleton {
INSTANCE;
public void yourMethod() {
}
}
內部類實現單例
當Singleton被加載時,其內部類并不會被初始化,故可以確保當 Singleton類被載入JVM時,不會初始化單例類。只有 getInstance() 方法調用時,才會初始化 instance。同時,由于實例的建立是時在類加載時完成,故天生對多線程友好,getInstance() 方法也無需使用同步關鍵字。
public class Singleton {
/**
* 類級的內部類,也就是靜態的成員式內部類,該內部類的實例與外部類的實例沒有綁定關系,
* 而且只有被調用到才會裝載,從而實現了延遲加載
*/
private static class SingletonHolder{
/**
* 靜態初始化器,由JVM來保證線程安全
*/
private static final Singleton instance = new Singleton();
}
/**
* 私有化構造方法
*/
private Singleton(){
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}