【設計模式】三、單例模式(10分鐘深度搞定)
目錄
前言
單例模式是保證任何情況下,都僅有一個例項,並提供全域性訪問的方法。
一、餓漢式
先上程式碼
/**
* 餓漢式
*/
public class Singleton1 {
private static final Singleton1 instance = new Singleton1();
/**
* 私有化構造方法 (防止手動new)
*/
private Singleton1(){}
public static Singleton1 getInstance(){
return instance;
}
}
餓漢式的關鍵在於instance作為類變數直接得到初始化,該方法能夠百分之百的保證同步,也就是說instance在多執行緒下也不可能被例項化兩次,但是instance被ClassLoader載入後可能很長時間才會被使用,那就意味著instance例項所開闢的空間的堆記憶體會駐留更久的時間。
如果一個類中的成員屬性比較少,所佔用的記憶體資源不多,餓漢式也未嘗不可。總結起來,餓漢式可以保證多執行緒下唯一的例項,getInstance效能也比較高,但是無法進行懶載入
。
二、懶漢式
/**
* 懶漢式
*/
public class Singleton2 {
private static Singleton2 instance = null;
/**
* 私有化構造方法
*/
private Singleton2() {
}
// 存線上程安全問題
public static Singleton2 getInstance() {
if (instance == null) {
instance = new Singleton2();
}
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() {
if (instance == null) {
instance = new Singleton2();
}
return instance;
}
採用懶漢式 + 資料同步方式既滿足了懶載入又能百分之百保證instance例項的唯一性,但是synchronized 關鍵字天生的排他性導致了getInstance方法只能在同一時刻被一個執行緒所訪問,效能低下。
四、懶漢式 + Double-Check
// 解決執行緒安全問題,提升效率
private static Singleton2 instance = null;
private String msg;
/**
* 私有化構造方法
*/
private Singleton2() {
msg = "初始化引數";
}
public static Singleton2 getInstance3() {
if (instance == null) {
synchronized (Singleton2.class){
if(instance == null){
instance = new Singleton2();
}
}
}
return instance;
}
當兩個執行緒發現null == instance
成立時,只有一個執行緒有資格進入同步程式碼塊,完成對instance的例項化,隨後的執行緒發現 null == instance 不成立則無須進行任何操作,以後對getInstance的訪問就不需要資料同步的保護了。
這種方式看起來那麼的完美,既滿足了懶載入,有保證instance例項的唯一性。Double-Check的方式提供了高效的資料同步策略,可以允許多個執行緒同時對getInstance進行訪問。但是這種方式有可能引起空指標異常,我們分析一下。
Singleton4的建構函式中,初始化了msg還有Singleton2自身,根據JVM指令重排序和Happens-Before規則,這兩者之間的例項化順序並無前後關係的約束,那麼極有可能instance最先被例項化,而msg並未完成例項化,未完成初始化的例項呼叫其他方法將會丟擲空指標異常。
五、Volatile + Double + Check
private volatile static Singleton2 instance = null;
private String msg;
/**
* 私有化構造方法
*/
private Singleton2() {
msg = "初始化引數";
}
public static Singleton2 getInstance3() {
if (instance == null) {
synchronized (Singleton2.class){
if(instance == null){
instance = new Singleton2();
}
}
}
return instance;
}
在instance前 加上 volatile的關鍵字,則可以防止重排序的發生。但終歸加了synchronized
,對效能依舊造成了影響。有沒有更好的方式呢?有!
六、Holder方式
public class Singleton3 {
private static Singleton3 instance = null;
private static class Holder{
private static final Singleton3 singleton3 = new Singleton3();
}
/**
* 私有化構造方法
*/
private Singleton3() {
}
public static final Singleton3 getInstance() {
return Holder.singleton3;
}
}
在Singleton6中並沒有instance的靜態變數,而是將其放在靜態內部類Holder類中,因此Singleton3初始化過程中並不會建立Singleton3的例項,Holder類中定義了Singleton3的靜態變數,並且直接進行了例項化,當Holder被直接引用的時候則會建立Singleton3的例項,該方法又是同步方法,保證了記憶體的可見性,JVM的順序性和原子性。Holder方式是單例設計最好的設計之一。但是!依然優缺點。
反射破壞單例
public static void main(String[] args) throws Exception {
// 無聊情況下進行破壞
Class<?> clazz = Singleton3.class;
// 獲取私有化構造方法
Constructor constructor = clazz.getDeclaredConstructor(null);
// 強制訪問
constructor.setAccessible(true);
// 暴力初始化兩次
Object o1 = constructor.newInstance();
Object o2 = constructor.newInstance();
System.out.println(o1);
System.out.println(o2);
System.out.println(o1 == o2);
}
執行結果
顯然我們建立出來了兩個例項。破壞了我們的初衷。我們來優化一次,在構造方法做限制,一旦重複建立,我們就拋異常。
public class Singleton3 {
private static class Holder{
private static final Singleton3 singleton3 = new Singleton3();
}
/**
* 私有化構造方法
*/
private Singleton3() {
if(Holder.singleton3 != null){
throw new RuntimeException("不允許建立多個例項");
}
}
public static final Singleton3 getInstance() {
return Holder.singleton3;
}
}
感覺上該單例已經完美了,然而還有可能被破壞。
序列化破壞單例
實現序列化
public class Singleton4 implements Serializable {
private static class Holder{
private static final Singleton4 singleton3 = new Singleton4();
}
/**
* 私有化構造方法
*/
private Singleton4() {
if(Holder.singleton3 != null){
throw new RuntimeException("不允許建立多個例項");
}
}
public static final Singleton4 getInstance() {
return Holder.singleton3;
}
}
測試程式碼
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 static class Holder{
private static final Singleton4 singleton3 = new Singleton4();
}
/**
* 私有化構造方法
*/
private Singleton4() {
if(Holder.singleton3 != null){
throw new RuntimeException("不允許建立多個例項");
}
}
public static final Singleton4 getInstance() {
return Holder.singleton3;
}
private Object readResolve(){
return Holder.singleton3;
}
}
再看執行效果
有興趣的同學可以檢視JDK的原始碼,發現實際上,這裡我們還是例項化了兩次,只不過第二次建立的物件沒有被返回而已。這樣也會造成記憶體的不必要浪費。
七、註冊式單例
註冊時單例就是將每個例項登記到某個地方,使用唯一標識來獲取例項。
列舉式單例
public enum Singleton5 {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static final Singleton5 getInstance() {
return INSTANCE;
}
}
驚喜的是,列舉類天生就防止反射破壞與序列化破壞,有興趣的同學,可以查閱JDK原始碼。
容器式單例
列舉類雖然寫法優雅,但是在類載入之時,就將所有物件初始化放在記憶體中,這其實與餓漢式
無異。容器式則是將Bean 放在 concurrentHashMap<String,Object>
中,詳細可參照Spring IOC的實現,這裡就不多做敘述了。
相關文章
- Javascript設計模式(三)單例模式JavaScript設計模式單例
- 設計模式 | 4分鐘搞懂10種設計模式設計模式
- 設計模式快速學習(三)單例模式設計模式單例
- 設計模式(三)----建立型模式之單例模式(一)設計模式單例
- [設計模式]單例設計模式設計模式單例
- 設計模式-單例模式設計模式單例
- [設計模式] 單例模式設計模式單例
- 設計模式 —— 單例模式設計模式單例
- 設計模式(單例模式)設計模式單例
- 設計模式——單例模式設計模式單例
- 設計模式--單例模式設計模式單例
- 設計模式 單例模式設計模式單例
- 設計模式之模板方法模式(三分鐘學會一個設計模式)設計模式
- 設計模式之狀態模式(三分鐘學會一個設計模式)設計模式
- 5分鐘學設計模式之《簡單工廠模式》設計模式
- 設計模式-單例模式、多例模式設計模式單例
- 設計模式之單例設計模式設計模式單例
- 設計模式一(單例模式)設計模式單例
- 設計模式之☞單例模式設計模式單例
- Java設計模式–單例模式Java設計模式單例
- Java設計模式——單例模式Java設計模式單例
- Java設計模式--單例模式Java設計模式單例
- js設計模式--單例模式JS設計模式單例
- Java設計模式 | 單例模式Java設計模式單例
- 設計模式之單例模式設計模式單例
- Java設計模式【單例模式】Java設計模式單例
- 設計模式之---單例模式設計模式單例
- 設計模式(二)——單例模式設計模式單例
- PHP設計模式_單例模式PHP設計模式單例
- 設計模式系列-單例模式設計模式單例
- 設計模式(一)_單例模式設計模式單例
- 設計模式1——單例模式設計模式單例
- 設計模式(七):單例模式設計模式單例
- PHP設計模式——單例模式PHP設計模式單例
- 設計模式—單例模式(轉)設計模式單例
- 常用設計模式-單例模式設計模式單例
- Java設計模式-單例模式Java設計模式單例
- 【淺談設計模式(三)】讓你一分鐘讀懂設計模式設計模式