设计模式03------单例模式
參考文獻:《Java與模式》
作為對象的創建模式,單例模式確保某一個類只有一個實例,而且自行實例化并向整個系統提供這個實例,這個類稱為單例類。
一. 單例模式的要點
顯然單例模式的要點有3個:
- 某個類只能有一個實例
- 它必須自行創建這個實例
- 它必須自行向整個系統提供這個實例
二. 單例模式分類
1. 餓漢式單例類
它是在Java語言里實現起來最為簡便的單例類。
public class EagerSingleton {private static final EagerSingleton instance=new EagerSingleton();/*** 私有的默認構造器*/private EagerSingleton() {}/*** 靜態工廠方法*/public static EagerSingleton getInstance() {return instance;}}說明:這個類被加載時,靜態變量instance會被初始化,此時類的私有構造器會被調用。這個時候,單例類的唯一實例就被創建出來了。
因為構造器是私有的,因此該類是不可以被繼承的。
2. 懶漢式單例類
與餓漢式單例類不同的是,懶漢式在第一次被引用時將自己實例化。如果加載器是靜態的,那么在懶漢式類被加載時不會將自己實例化。
public class LazySingleton {private static LazySingleton instance=null;/*** 私有構造器*/private LazySingleton() {}/*** 靜態工廠方法,返回此類的唯一實例*/synchronized public static LazySingleton getInstance() {if(instance==null) {instance=new LazySingleton();}return instance;}}說明:在上面給出的懶漢式單例類中對靜態工廠使用了同步化,以處理多線程環境。有些設計師在這里建議使用所謂的”雙重檢查成例“。必須指出的是,”雙重檢查成例“不可以在Java語言中使用。
餓漢式相比懶漢式,它的資源利用效率差一些,但是速度和反應時間上卻很快。懶漢式在實例化時,必須處理好在多個線程同時首次引用此類時的訪問限制問題,特別時當單例類作為資源控制器在實例化時必然涉及資源初始化,而資源初始化很可能耗費時間。這意味著出現多線程同時首次引用此類的幾率變得較大。
3.登記式單例類
這個類是為了克服前兩種不可以被繼承的缺點而設計的。
public class RegSingleton {static private HashMap registry=new HashMap();static {RegSingleton instance=new RegSingleton();registry.put(instance.getClass().getName(), instance);}/*** 保護的默認構造器*/protected RegSingleton() {}/*** 靜態工廠方法,返回此類的唯一的實例*/public static RegSingleton getInstance(String name) {if(name==null) {//key is nullname="com.test.b.RegSingleton";}if(registry.get(name)==null) {//value is nulltry {registry.put(name, Class.forName(name).newInstance());//利用的反射機制,創造一個單利對象加入registry}catch (Exception e) {System.out.println("Error happend");}}return (RegSingleton) registry.get(name);}/*** 一個示意性的商業方法*/public String about() {return "Hello, I am RegSingleton";}}它的子類RegSingletonChild需要父類的幫助才能實例化。
public class RegSingletonChild extends RegSingleton{public RegSingletonChild() {}/*** 靜態工廠方法*/public static RegSingletonChild getInstance() {return (RegSingletonChild) RegSingleton.getInstance("com.test.b.RegSingletonChild");}/*** 一個示意性的商業方法*/public String about() {return "Hello, I am RegSingletonChild";} }缺點:父類的實例必須存在才可能有子類的實例
三. 雙重檢查成例的研究
雙重檢查成例(Double Check Idiom)是從C語言移植過來的一種代碼模式。具體分析如下:
1. 未使用任何線程安全考慮的錯誤例子
首先考慮一個單線程的版本,代碼如下:
public class Foo {private Helper helper=null;public Helper getHelper() {if(helper==null) {helper=new Helper();}return helper;} }說明:這是一個錯誤的例子。寫出這樣的代碼,本意顯然是要保證在整個JVM中只有一個Helper的實例。因此,才會有if(helper==null)的檢查。非常明顯的是,如果在多線程的環境中運行,上面的代碼會有兩個甚至兩個以上的helper對象被創建出來,從而造成錯誤。想象一下在多線程中的情況。假設兩個線程A和B幾乎同時到達if(helper==null)語句的外面,假設A稍微比B早一點點,則:
2. 線程安全的版本
public class Foo {private Helper helper=null;public synchronized Helper getHelper() {if(helper==null) {helper=new Helper();}return helper;} }3. 畫蛇添足的”雙重檢查“
仔細審查上面的正確答案會發現,同步化實際上只是在helper變量第一次被賦值之前才有用。在helper變量有了值以后,同步化實際上變成了一個不必要的瓶頸。如果有一個方法減去這個小小的額外開銷,不是更加完美嗎?所以”雙重檢查“來了,它是反面教材,在Java編譯器里面無法實現。
public class Foo {private Helper helper=null;public Helper getHelper() {if(helper==null) {//第一次檢查(位置1)//這里會有多余一個的線程同時到達(位置2)synchronized (this) {//這里在每個時刻只能有一個線程(位置3)if(helper==null) {//第二次檢查(位置4)helper=new Helper();}} }return helper;} }這是錯誤的例子:因為Java編譯器并不支持這種雙重機制。為什么呢?
不能工作的根本原因在于,在Java編譯器中,LazySingleton類的初始化與m_instance變量賦值的順序不可預料。如果一個線程在沒有同步化的條件下讀取m_instance引用,并調用這個對象的方法的話,可能會發現對象的初始化過程尚未完成,從而造成崩潰。因此,雙重檢查成立對Java語言來說是不成立的。
?
轉載于:https://www.cnblogs.com/Hermioner/p/10014136.html
總結
以上是生活随笔為你收集整理的设计模式03------单例模式的全部內容,希望文章能夠幫你解決所遇到的問題。

- 上一篇: npm更新模块并同步到package.j
- 下一篇: 2018-11-25