天天看點

單例模式(四種)與其破壞方式(三種)單例模式

單例模式

單例模式是指在記憶體中隻會建立且僅建立一次對象的設計模式。在程式中多次使用同一個對象且作用相同時,為了防止頻繁地建立對象使得記憶體飙升,單例模式可以讓程式僅在記憶體中建立一個對象,讓所有需要調用的地方都共享這一單例對象。

單例模式的類型

單例模式有兩種類型:

  • 懶漢式:在真正需要使用對象時才去建立該單例類對象
  • 餓漢式:在類加載時已經建立好該單例對象,等待被程式使用

餓漢式:

  • 線程安全
  • 不能實作延遲加載(浪費空間)
public class Singleton {

    private static Singleton instance = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return instance;
    }
} 
           

不管程式是否需要這個對象的執行個體,總是在類加載的時候就先建立好執行個體,了解起來就像不管一個人想不想吃東西都把吃的先買好,如同餓怕了一樣。

懶漢式:

最初懶漢式0

  • 延遲加載
  • 非線程安全
public class Singleton {

    private static Singleton instance = null;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (null == instance) {
            instance = new Singleton();
        }
        return instance;
    }
}   
           

如果一個對象使用頻率不高,占用記憶體還特别大,明顯就不合适用餓漢式了,這時就需要一種懶加載的思想,當程式需要這個執行個體的時候才去建立對象,就如同一個人懶的餓到不行了才去吃東西。

懶漢式1

  • 延遲加載
  • 線程安全 - 但每次擷取都需要加鎖,性能低下
public class Singleton {
	private static Singleton instance = null;
	private Singleton() {
 
	}
	public static  synchronized Singleton getInstance() {
		if (instance == null)
		  instance = new Singleton()
		}
		return instance;
	}
           

懶漢式2(雙重檢查加鎖):

  • 延遲加載
  • 線程安全 - 效率相比1提升,建立後不必每次加鎖
public class Singleton {
	private volatile static Singleton instance = null;
	private Singleton() {
 
	}
 
	public static Singleton getInstance() {
		if (instance == null) {
			synchronized (Singleton.class) {
				if (instance == null) {
					instance = new Singleton();
				}
			}
 
		}
		return instance;
	}
 
}
           

使用靜态内部類實作單例模式

  • 延遲加載
  • 線程安全(利用類加載機制,保證線程安全) : 雙親委派機制保證一個類隻加載一次,進而保證線程安全
public class Singleton {
	private static class SingletonHoler {
		/**
		 * 靜态初始化器,由JVM來保證線程安全
		 */
		private static Singleton instance = new Singleton();
	}
 
	private Singleton() {
	}
 
	public static Singleton getInstance() {
		return SingletonHoler.instance;
	}
 
}
           

使用枚舉來實作單例

public enum Singleton {
	uniqueInstance;// 定義一個枚舉的元素,它 就代表了Singleton的一個執行個體
 
	public void singletonOperation() {
		// 功能處理
		System.err.println("功能處理");
	}
 
}
           

破壞單例模式的三種方式

  1. 反射
  2. 序列化
  3. 克隆

解決方案如下:

1、防止反射

定義一個全局變量,當第二次建立的時候抛出異常

private static volatile boolean isCreate = false;//預設是第一次建立
		/**
		 * 1.構造方法私有化,外部不能new
		 */
		private Singleton() {
			if(isCreate) {
				throw new RuntimeException("已然被執行個體化一次,不能在執行個體化");
			}
			isCreate = true;
		}
           

2、防止克隆破壞

重寫clone(),直接傳回單例對象

@Override
		 protected Object clone() throws CloneNotSupportedException {
		     return instance;
		 }
           

3、防止序列化破壞

從序列化中恢複一個單例對象會破壞單例模式,解決方法是添加

readResolve()

原理:

  1. 反序列化時,首先擷取序列化的類 : desc( 可了解為單例類的class類,但它和JVM加載到記憶體中的單例class類有不同)因為如果desc就是我們的單例class類,那是不允許再執行個體化的。而desc類卻可以執行個體化。
  2. 判斷對象是否能執行個體化。可以則進行執行個體化,至此單例類進行了第一次執行個體化,對象名為obj
  3. 第一次執行個體化完成後,通過反射尋找該單例類中的readResolve()方法,沒有則直接傳回obj對象。(這就是直接反序列化破壞單例模式的原因)
  4. 有定義readResolve()方法,desc通過invokeReadResolve(Object obj)方法調用readResolve()方法擷取單例對象instance,将他指派給rep,如果單例對象之前已經被執行個體化過,那麼rep就會指向之前執行個體化的單例對象。如果我們之前沒有執行個體化單例對象,則rep會指向null。
  5. rep與obj進行比較,由于obj是反射擷取的對象,當然與rep不等,于是将rep的值instance指派給obj,将obj傳回,傳回對象instance也就保證了單例。

簡而言之就是,當我們通過反序列化readObject()方法擷取對象時會去尋找readResolve()方法,如果該方法不存在則直接傳回新對象,如果該方法存在則按該方法的内容傳回對象,以確定如果我們之前執行個體化了單例對象,就傳回該對象。如果我們之前沒有執行個體化單例對象,則會傳回null。

參考文章

/**
		 * 防止序列化破環
		 * @return
		 */
		private Object readResolve() {
	        return instance;
	    }

           

最終版

private static class Singleton  implements Serializable,Cloneable{
		
		private static volatile boolean isCreate = false;//預設是第一次建立
		/**
		 * 1.構造方法私有化,外部不能new
		 */
		private Singleton() {
			if(isCreate) {
				throw new RuntimeException("已然被執行個體化一次,不能在執行個體化");
			}
			isCreate = true;
		}
		
		
		//2.本類内部建立對象執行個體
		private static  volatile  Singleton instance;
		
		
		//3.提供一個公有的靜态方法,傳回執行個體對象
		public static  Singleton getInstance() {
			if(instance == null) {
				synchronized (Singleton.class) {
					if(instance == null) {
						instance = new Singleton();
					}
				}
			}
			return instance;
		}
		
		@Override
		 protected Object clone() throws CloneNotSupportedException {
		     return instance;
		 }
		
		/**
		 * 防止序列化破環
		 * @return
		 */
		private Object readResolve() {
	        return instance;
	    }
		 
	}

           

繼續閱讀