七、註冊式單例
單例模式是保證任何情況下,都僅有乙個例項,並提供全域性訪問的方法。
先上**
/**
* 餓漢式
*/public class singleton1
public static singleton1 getinstance()
}
餓漢式的關鍵在於instance作為類變數直接得到初始化,該方法能夠百分之百的保證同步,也就是說instance在多執行緒下也不可能被例項化兩次,但是instance被classloader載入後可能很長時間才會被使用,那就意味著instance例項所開闢的空間的堆記憶體會駐留更久的時間。
如果乙個類中的成員屬性比較少,所占用的記憶體資源不多,餓漢式也未嘗不可。總結起來,餓漢式可以保證多執行緒下唯一的例項,getinstance效能也比較高,但是無法進行懶載入
。
/**
* 懶漢式
*/public class singleton2
public static singleton2 getinstance()
return instance;}}
singleton2 的類變數instance = null,當singleton2.class被初始化的時候instance並不會被例項化,在getinstance方法中會判斷instance 例項是否被例項化,看起來沒什麼問題,但在多執行緒環境下,會導致instance可能被例項化多次。 執行緒1判斷null == instance
為true時,還沒有例項化instance,切換到了執行緒2執行,執行緒2判斷null == instance也為true。就會例項化多次。
// 解決執行緒安全問題,但是效率低
public synchronized static singleton2 getinstance2()
return instance;
}
採用懶漢式 + 資料同步方式既滿足了懶載入又能百分之百保證instance例項的唯一性,但是synchronized 關鍵字天生的排他性導致了getinstance方法只能在同一時刻被乙個執行緒所訪問,效能低下。
// 解決執行緒安全問題,提公升效率
private static singleton2 instance = null;
private string msg;
/*** 私有化構造方法
*/private singleton2()
public static singleton2 getinstance3() }}
return instance;
}
當兩個執行緒發現null == instance
成立時,只有乙個執行緒有資格進入同步**塊,完成對instance的例項化,隨後的執行緒發現 null == instance 不成立則無須進行任何操作,以後對getinstance的訪問就不需要資料同步的保護了。
這種方式看起來那麼的完美,既滿足了懶載入,***instance例項的唯一性。double-check的方式提供了高效的資料同步策略,可以允許多個執行緒同時對getinstance進行訪問。但是這種方式有可能引起空指標異常,我們分析一下。
private volatile static singleton2 instance = null;
private string msg;
/*** 私有化構造方法
*/private singleton2()
public static singleton2 getinstance3() }}
return instance;
}
在instance前 加上 volatile的關鍵字,則可以防止重排序的發生。但終歸加了synchronized
,對效能依舊造成了影響。有沒有更好的方式呢?有!
public class singleton3
/*** 私有化構造方法
*/private singleton3()
public static final singleton3 getinstance()
}
在singleton6中並沒有instance的靜態變數,而是將其放在靜態內部類holder類中,因此singleton3初始化過程中並不會建立singleton3的例項,holder類中定義了singleton3的靜態變數,並且直接進行了例項化,當holder被直接引用的時候則會建立singleton3的例項,該方法又是同步方法,保證了記憶體的可見性,jvm的順序性和原子性。holder方式是單例設計最好的設計之一。但是!依然優缺點。
public static void main(string args) throws exception
執行結果
顯然我們建立出來了兩個例項。破壞了我們的初衷。我們來優化一次,在構造方法做限制,一旦重複建立,我們就拋異常。
public class singleton3
/*** 私有化構造方法
*/private singleton3()
}public static final singleton3 getinstance()
}
感覺上該單例已經完美了,然而還有可能被破壞。
實現序列化
public class singleton4 implements serializable
/*** 私有化構造方法
*/private singleton4()
}public static final singleton4 getinstance()
}
測試**
singleton4 s1 = null;
singleton4 s2 = singleton4.getinstance();
fileoutputstream fos = new fileoutputstream("singleton4.obj");
objectoutputstream oos = new objectoutputstream(fos);
oos.writeobject(s2);
oos.flush();
oos.close();
fileinputstream fis = new fileinputstream("singleton4.obj");
objectinputstream ois = new objectinputstream(fis);
s1 = (singleton4) ois.readobject();
ois.close();
system.out.println(s1);
system.out.println(s2);
system.out.println(s1 == s2);
執行結果
顯然,單例又遭到破壞。如何解決呢?只需要新增readresolve
方法即可。
public class singleton4 implements serializable
/*** 私有化構造方法
*/private singleton4()
}public static final singleton4 getinstance()
private object readresolve()
}
再看執行效果
有興趣的同學可以檢視jdk的原始碼,發現實際上,這裡我們還是例項化了兩次,只不過第二次建立的物件沒有被返回而已。這樣也會造成記憶體的不必要浪費。
註冊時單例就是將每個例項登記到某個地方,使用唯一標識來獲取例項。
public enum singleton5
public void setdata(object data)
public static final singleton5 getinstance()
}
驚喜的是,列舉類天生就防止反射破壞與序列化破壞,有興趣的同學,可以查閱jdk原始碼。
列舉類雖然寫法優雅,但是在類載入之時,就將所有物件初始化放在記憶體中,這其實與餓漢式
無異。容器式則是將bean 放在concurrenthashmap
中,詳細可參照spring ioc的實現,這裡就不多做敘述了。
一分鐘讀懂設計模式 單例模式
單例模式只允許建立乙個物件,因此節省記憶體,加快物件訪問速度。1.需要頻繁例項化然後銷毀的物件。2.建立物件時耗時長,耗資源,又經常用到的物件。3.頻繁訪問資料庫或檔案的物件 4.工具類物件 在類載入的時候不被初始化 不加synchronized執行緒不安全,加synchronized保證執行緒安全...
一分鐘入門設計模式之單例模式
設計模式理論上有 3類23種 定義 單例模式是一種物件建立模式,用於生產乙個物件的例項,它可以確保系統中乙個類只產生乙個例項。單例的好處 單例模式理論上有 5類8種 餓漢模式 靜態常量 優點 寫法簡單,類載入時已經完成了例項化,避免了執行緒同步問題 缺點 會造成記憶體浪費 建立步驟 私有化構造器 本...
設計模式 10 單例模式
單例模式 singleton 簡介 單例模式保證乙個類僅有乙個例項,並提供乙個訪問它的全域性訪問點。使用單例模式能夠讓設計師快速獲取提供某項服務或者功能的物件,可以省去層層傳遞物件的困擾。單例模式在實現時,需要程式語言的支援,需要程式語言具有靜態類屬性 靜態類方法以及可重新定義建構函式的訪問修飾符,...