Java單例模式的實現與破壞

單例模式是一種設計模式,是在整個運行過程中只需要產生一個實例。那麼怎樣去創建呢,以下提供了幾種方案。

一、創建單例對象

懶漢式

public class TestSingleton {
	
	// 構造方法私有化
	private TestSingleton(){}
	
	// 聲明實例
	private static TestSingleton singleton;
	
	// 提供外部調用方法,生成並獲取實例
	public static TestSingleton getInstance() {
		if(singleton == null) {
			singleton = new TestSingleton();
		}
		return singleton;
	}
}

此方案是以時間換空間,啓動時並不會執行任何操作,只有被調用時,採取實例化對象。不過這種方法在多線程下不安全,因爲兩個線程如果同時調用時,會同時通過非空驗證的驗證,造成創建兩個對象的後果,有悖設計初衷。

針對多線程問題,應該加入雙重非空判斷:

public class TestSingleton {
	
	// 構造方法私有化
	private TestSingleton(){}
	
	// 聲明實例
	private static volatile TestSingleton singleton;
	
	// 提供外部調用方法,生成並獲取實例
	public static TestSingleton getInstance() {
		if(singleton == null) {
			synchronized (TestSingleton.class) {
				if(singleton == null) {
					singleton = new TestSingleton();
				}
			}
		}
		return singleton;
	}
}

餓漢式

public class TestSingleton {
	
	// 構造方法私有化
	private TestSingleton(){}
	
	// 聲明並生成實例
	private static TestSingleton singleton = new TestSingleton();
	
	// 提供外部調用方法,獲取實例
	public static TestSingleton getInstance() {
		return singleton;
	}
}

以空間換時間,類一加載時,就對其進行實例化,後面調用時直接提供對象實例。

靜態內部類實現懶加載

public class TestSingleton {
	
	// 構造方法私有化
	private TestSingleton(){}
	
	private static class TestSingletonFactory{
		private static TestSingleton singleton = new TestSingleton();
	}
	
	// 提供外部調用方法,獲取實例
	public static TestSingleton getInstance() {
		return TestSingletonFactory.singleton;
	}
}

當getInstance方法被調用時,纔會初始化靜態內部類TestSingletonFactory的靜態變量singleton。此處由JVM來保障線程安全。

二、破壞單例

實現單例後,按照預期結果應該所有對象都是同一個對象。但是以下有幾種情況可以破壞單例的性質。

首先讓單例類實現Serializable, Cloneable接口,以便實驗。

public class TestSingleton implements Serializable, Cloneable{

	private static final long serialVersionUID = 1L;

	// 構造方法私有化
	private TestSingleton(){}

	private static class TestSingletonFactory{
		private static TestSingleton singleton = new TestSingleton();
	}

	// 提供外部調用方法,獲取實例
	public static TestSingleton getInstance() {
		return TestSingletonFactory.singleton;
	}
}
  • 序列化
// 獲取實例
TestSingleton originSingleton = TestSingleton.getInstance();
// 寫出對象
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(originSingleton);
// 寫入對象
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
TestSingleton serializeSingleton = (TestSingleton) ois.readObject();
// 判斷兩個對象是否相等
System.out.println(originSingleton == serializeSingleton); // false
  • 反射
// 反射
Class<TestSingleton> clazz = TestSingleton.class;
// 獲取無參構造函數
Constructor<TestSingleton> constructor = clazz.getDeclaredConstructor();
// 將私有設置爲可見
constructor.setAccessible(true);
// 用構造器生成實例
TestSingleton instance = constructor.newInstance();
// 判斷兩個對象是否相等
System.out.println(originSingleton == instance); // false
  • 克隆
// 克隆
TestSingleton clone = (TestSingleton) originSingleton.clone();
System.out.println(originSingleton == clone); // false

三、修復破壞

對於這種預料之外的結果,我們應該怎樣去控制呢?

  • 序列化

添加readResolve方法,返回Object。

  • 反射

添加全局可見變量,如果再次調用構造方法生成實例時,拋出運行時錯誤。

  • 克隆

重寫clone方法,直接返回單例對象。

public class TestSingleton implements Serializable, Cloneable{

	private static final long serialVersionUID = 1L;
	
	private static volatile boolean isCreated = false;//默認是第一次創建

	// 構造方法私有化
	private TestSingleton(){
		if(isCreated) {
			throw new RuntimeException("實例已經被創建");
		}
		isCreated = true;
	}

	private static class TestSingletonFactory{
		private static TestSingleton singleton = new TestSingleton();
	}

	// 提供外部調用方法,獲取實例
	public static TestSingleton getInstance() {
		return TestSingletonFactory.singleton;
	}
	
	@Override
	protected Object clone() throws CloneNotSupportedException {
		return getInstance();
	}

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章