大家都知道單例模式,單例模式(singleton pattern)是指確保乙個類在任何情況下都絕對只有乙個例項,並提供乙個全域性訪問點。單例模式是建立型模式。具體做法是將構造器私有化,不讓其他類呼叫構造器,即其他類不能通過new的方式建立物件。但是反射,序列化,轉殖等操作都有可能會破壞單利模式。
現在看乙個問題,建立乙個類的物件有幾種方式呢?
由轉殖我們可以想到原型模式,原型模式就是**根據給定某個類的物件(稱為原型),複製出該類的另乙個物件,複製的物件與原型物件有著相同的變數值。**我們可以直接手動去new乙個新的物件,然後set原物件的屬性值到新物件的屬性中,也可以直接通過重寫object類中的clone方法。
就是通過clone方法實現物件的建立的,clone方法是object類中protected方法。我們知道,protected方法子類直接呼叫,哪怕不是同一包下也能呼叫。
但是object類中的clone方法特殊,不能夠讓子類直接呼叫。一般都都是子類重寫該方法,並且子類需要實現cloneable介面,這樣才可以對該類進行轉殖,如果子類不實現cloneable介面,那麼在呼叫clone方法會丟擲clonenotsupportedexception異常
public
class
person
implements
cloneable
//省略get,set方法
}/**
* 淺轉殖。
* @throws exception
*/@test
public
void
test01()
throws exception
//執行結果如下:
person
person
如果是這樣的話,那麼上面說的單例模式失效了?當然答案是否定,某乙個物件直接呼叫clone方法,會丟擲異常,即並不能成功轉殖乙個物件。呼叫該方法時,必須實現乙個cloneable 介面。這也就是原型模式的實現方式。還有即如果該類實現了cloneable介面,儘管建構函式是私有的,他也可以建立乙個物件。即clone方法是不會呼叫建構函式的,他是直接從記憶體中copy記憶體區域的。所以單例模式的類是不可以實現cloneable介面的。
/**
@author 青冰白夜
最簡單的單例模式,這裡就先不考慮執行緒安全問題,主要是描述下單例破壞的情況
*/public
class
singleton
private
static singleton instance=null;
public
static singleton getinstance()
return instance;}}
/**測試類
*/@test
public
void
test02()
throws exception
//執行結果如下:
private
singleton()
true
private
singleton()
false
單例模式的構造方法除了加上 private 以外,沒有做任何處理。如果我們使用反射來呼叫其構造方法,然後,再呼叫 getinstance()方法,應該就會兩個不同的例項。由上面的測試可知,即便將構造器私有化了,還是會被反射呼叫,反射就這樣破壞了單例。下面介紹解決方法。
/**
* @author zhouxingcan
*/public
class
singleton
system.out.
println
("private singleton()");
}public
static singleton getinstance()
return instance;
}}
上述**考慮的是,在構造器中,判斷例項是否為null,若不是就直接丟擲異常。在這裡很多人可能會說這樣看似可以防止反射呼叫私有構造,其實還有一點需要注意,反射照樣可以修改私有變數的值,即可以修改例項變數instance為null,這樣照樣可以呼叫私有構造器了。
這麼說是沒有錯的,但是呢,正常情況下,外界是不知道該單例的例項變數的名字。我也查閱了很多資料,也都沒有提到這個問題,所有在此可以先忽略。如果後續發現更好的方式,會持續更新部落格。
@test
public
void
test02()
throws exception
//執行結果如下:
private
singleton()
true
com.zxc.pattern.原型模式.singleton@621be5d1
--com.zxc.pattern.原型模式.singleton@621be5d1
private
singleton()
false
/**
使用內部類的方式,
*/public
class
lazyinnerclasssingleton
}//每乙個關鍵字都不是多餘的
//static 是為了使單例的空間共享
//保證這個方法不會被重寫,過載
public
static
final lazyinnerclasssingleton getinstance()
//預設不載入
private
static
class
lazyholder
}
這種內部類實現單例的方式,是絕對執行緒安全的,既可以防止高併發情況下執行緒安全問題,又可以實現保護單例。
當我們將乙個單例物件建立好,有時候需要將物件序列化然後寫入到物理記憶體,下次使用時再從物理記憶體中讀取到物件,反序列化轉化為jvm記憶體物件。反序列化後的物件會重新分配jvm記憶體,即重新建立。那如果序列化的目標的物件為單例物件,就違背了單例模式的初衷,相當於破壞了單例。
來看一段**:
/**
需要序列化的類
*/public
class
singletonserializable
implements
serializable
public
static singletonserializable getinstance()
return instance;}}
/**測試類
*/public
class
singletondemo
}//輸出結果如下:
單例模式.singletonserializable@330bedb4
單例模式.singletonserializable@16b98e56
}
執行結果中,可以看出,反序列化後的物件和手動建立的物件是不一致的,例項化了兩次,違背了單例的設計初衷。那麼,我們如何保證序列化的情況下也能夠實現單例?其實很簡單,只需要增加 readresolve()方法即可。來看優化**:
public
class
singletonserializable
implements
serializable
private
singletonserializable()
public
static singletonserializable getinstance()
return instance;
}private object readresolve()
}
再看執行結果
com.zxc.pattern.單例模式.singletonserializable@45ee12a7
com.zxc.pattern.單例模式.singletonserializable@45ee12a7
兩個是同乙個物件,說明可行。看起來簡單,但是為什麼呢?這個等後續更新註冊式單例,以及jdk中objectoutputstream原始碼分析才能深入理解原因,具體後續分享。
單例模式,解決單例破壞。
破壞單例模式的三種方法 執行緒安全情況下 單例模式有 3 個特點 單例類只有乙個例項物件 該單例物件必須由單例類自行建立 單例類對外提供乙個訪問該單例的全域性訪問點。單例模式的優點和缺點 單例模式的優點 單例模式可以保證記憶體裡只有乙個例項,減少了記憶體的開銷。可以避免對資源的多重占用。單例模式設定...
附 單例模式的破壞
序列化物件對單例模式的破壞與恢復 首先這是乙個餓漢式的單例物件構建方式,一般情況下獲取到的都是同乙個單例物件 但是當序列化寫入本地再讀入記憶體時,會重新建立乙個單例物件 為什麼會在讀入序列化後的物件時會讓單例模式失效呢?這兒從readobject 方法入手 進入這個方法後 private 進入pri...
單例模式的破壞及任何防止被破壞
常用的單例模式有懶漢式 餓漢式兩種情況。實際的應用場景也是很常見,好比如資料庫連線池的設計,還有windows的task manager 任務管理器 等。所謂單例模式就是,某乙個類只能有乙個例項,實現的核心就是將類的建構函式私有化,只能由該類建立物件,其他物件就不能呼叫該類的建構函式,即不能建立物件...