單例模式是設計模式中最常用到的一種模式,一般應用于定位管理、線程管理、文件管理、網絡管理等類上面,讓這些類的單一實例來處理App的各個模塊邏輯等。
這里我就不多舉例子了,相信大家應該都會用,我就將基本所有類型的單例實現方式列舉出來,供大家參考吧。
(1)幾種常見的單例模式模板
- 餓漢單例
“餓漢”,指的是代碼很早就想要初始化實例出來,于是,此形式的類實例在類編譯加載在內存中的時候就初始化創建完畢。
public class Singleton{
private static Singleton sInstance = new SingleTon();
private Singleton() {}
public Singleton getInstance(){
return sInstance;
}
}
優點:類加載時實例化對象,避免了多線程同步創建問題。
缺點:類加載時就進行初始化,即還沒用到它,它就占了App進程的部分內存,造成一定的內存浪費。【沒有懶加載】
- 懶漢單例(加了
synchronized
,保證線程安全)
“懶漢”,相對于“餓漢”來說,就不是在編譯時急著初始化了,而是在調用到類靜態方法要用到一個實例時,他再初始化單例對象。
public class LazySingleTon {
private static LazySingleTon sInstance;
private LazySingleTon(){
}
public static synchronized LazySingleTon getInstance(){
if(sInstance==null){
sInstance = new LazySingleTon();
}
return sInstance;
}
}
注意,以上的code,方法加synchronized
與否,直接影響讀取性能。
- DCL 單例(雙重檢查鎖定)
public class DCLSingleTon {
// private static DCLSingleTon sInstance;
private volatile static DCLSingleTon sInstance = null;
///考慮到 DCL失效問題:JDK1.5之前的JMM(Java內存模型)中的Cache、寄存器到主內存回寫順序的規定,上面三件事中的后面兩件事的順序無法保證。
///這樣,如果是執行1->3->2的執行順序,那么,在內部成員都沒有被初始化的情況下,sInstance就已經被賦值為非null了,那就后面會產生錯誤了。
///于是,>=JDK1.5時,可以這樣:
// private volatile static DCLSingleTon sInstance = null;
///雖然這樣,加載時會影響性能,但是,還是值得的。
///這樣一來,就可以每次在主內存中讀取對象了。
private DCLSingleTon(){
}
public static DCLSingleTon getInstance(){
if(sInstance== null){
synchronized (DCLSingleTon.class) {
if(sInstance == null){
sInstance = new DCLSingleTon();
}
}
}
return sInstance;
}
}
- 優點:既能夠在需要時才初始化單例【懶加載】,又能夠保證線程安全,而且,單例對象初始化后的getInstance調用是不會進行同步鎖的【第一次要初始化對象所以慢,第二次之后就很快】。
- 缺點:加入了
volatile
關鍵字(JDK1.5以上),保證Java編譯器執行順序,但影響了性能。【不過,這個影響很小】
- 靜態內部類單例【推薦】
public class StaticSingleTon {
private static StaticSingleTon sInstance;
private StaticSingleTon(){}
public static StaticSingleTon getInstance(){
return StaticSingleTon.sInstance;
}
/// 靜態內部類
private static class SingleTonHolder{
private static final StaticSingleTon sInstance = new StaticSingleTon();
}
}
- 特點:同樣 是 只有第一次調用
getInstance()
時,才會加載SingleTonHolder
類,也才會初始化對象。 - 解決了DCL亂序問題【不怕他會執行亂序。一定是先初始化對象,然后獲取對象。】
- 枚舉單例
public enum EnumSingleTon {
DOG,CAT;
public void bark(){
System.out.println(toString()+"吠了一聲!");
}
}
然后,我們可以直接調用:
DOG.bark();
DOG.bark();
CAT.bark();
CAT.bark();
永遠只存在一條狗和一只貓的實例。
- 優點:解決了對象反序列化問題(任何時候都是單例)
- 什么是“反序列化問題”? 答:就是,即使你的單例類的構造器是
private
的,但是到反序列化那一步的時候,依然會通過特殊手段去調用該方法,來實例化一個新的實例。【so,將對象寫入磁盤,再讀取出來的過程,就會新建對象,而不是用回原來的實例。】
- 使用容器
使用容器,同樣能實現單例模式。
public class SingleTonManager {
private static Map<String, Object> objMap = new HashMap<String,Object>();
private SingleTonManager(){}
public static void registerService(String key , Object instance){
if(! objMap.containsKey(key)){
objMap.put(key, instance);
}
}
public static Object getInstance(String key){
return objMap.get(key);
}
}