單例模式是一種設計模式,是在整個運行過程中只需要產生一個實例。那麼怎樣去創建呢,以下提供了幾種方案。
一、創建單例對象
懶漢式
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();
}
}