單例模式(二)為什麼單例模式會被破壞? 怎麼解決?
阿新 • • 發佈:2020-12-27
技術標籤:java大雜燴軟體架構和設計模式破壞單例模式單例模式java序列化
文章目錄
1. 反射破壞單例模式
假如我們特別想通過反射獲取私有的構造方法,然後再通過.newInstance()方法來建立該類的例項,就會產生兩個不同的例項。
靜態內部類建立單例模式
package patternsDesign.singleTonDetail;
public class LazyInnerClassSingleton {
//1. 私有構造器
private LazyInnerClassSingleton() {
}
//2. 靜態內部類初始化類的例項
private static final class InnerClass {
private static final LazyInnerClassSingleton singleTon = new LazyInnerClassSingleton();
}
//3.提供訪問的方法
public static final LazyInnerClassSingleton getInstance () {
return InnerClass.singleTon;
}
}
通過反射破壞單例模式
package patternsDesign.singleTonDetail;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Client {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// LazyInnerClassSingleton singleton=new LazyInnerClassSingleton();
//通過反射建立例項
Class<?> clazz = LazyInnerClassSingleton.class;
//通過反射來獲取私有構造方法
Constructor c = clazz.getDeclaredConstructor(null);
c.setAccessible(true);
//暴力初始化
Object o1 = c.newInstance();
Object o2 = c.newInstance();
System.out.println("o1=" + o1 + "\no2=" + o2);
System.out.println("是否相等:" + o1 == o2);
}
}
列印結果:
o1=patternsDesign.singleTonDetail.LazyInnerClassSingleton@45ee12a7
o2=patternsDesign.singleTonDetail.LazyInnerClassSingleton@330bedb4
false
由上結果可以發現,靜態內部類雖然能夠保證執行緒安全的形式保證全域性唯一,但是我們通過反射手段仍然能夠破壞單例, 因為通過反射來獲取例項時,建立了兩個不同的例項!
解決方法
在構造方法來新增一些限制,如果存在例項,那麼就丟擲異常!
package patternsDesign.singleTonDetail;
public class LazyInnerClassSingleton {
//1. 私有構造器
private LazyInnerClassSingleton() {
if (InnerClass.singleTon != null) {
throw new RuntimeException("不允許建立多個單例!");
}
}
//2. 靜態內部類初始化類的例項
private static final class InnerClass {
private static final LazyInnerClassSingleton singleTon = new LazyInnerClassSingleton();
}
//3.提供訪問的方法
public static final LazyInnerClassSingleton getInstance() {
return InnerClass.singleTon;
}
}
重新執行程式,觀察結果:
由此我們可以通過在構造方法里加限制的方式,解決了反射破壞例項的問題!
2. 通過序列化破壞單例模式
靜態內部類的形式建立單例模式
package patternsDesign.seriableSingleTon;
import java.io.Serializable;
public class SerializeSingleTon implements Serializable {
private static final long serialVersionUID = -3737338076212523007L;
//1. 私有構造器
private SerializeSingleTon() {
if (InnerClass.singleTon != null) {
throw new RuntimeException("不允許建立多個單例!");
}
}
//2. 靜態內部類初始化類的例項
private static final class InnerClass {
private static final SerializeSingleTon singleTon = new SerializeSingleTon();
}
//3.提供訪問的方法
public static final SerializeSingleTon getInstance() {
return InnerClass.singleTon;
}
}
通過序列化來獲取例項
package patternsDesign.seriableSingleTon;
import patternsDesign.singleTonDetail.LazyInnerClassSingleton;
import java.io.*;
public class Client {
public static void main(String[] args) {
SerializeSingleTon s1 = null;
SerializeSingleTon s2 = SerializeSingleTon.getInstance();
//通過序列化的形式破壞單例模式
try {
//1. 通過FileOutPutStream和ObjectOutputStream將物件寫入到檔案裡
FileOutputStream fos = new FileOutputStream("SerializeSingleTon.obj");
ObjectOutputStream obs = new ObjectOutputStream(fos);
obs.writeObject(s2);
obs.flush();
obs.close();
//2. 通過FileInputStream和ObjectInputStream將檔案中的物件讀入到s1中
FileInputStream fis = new FileInputStream("SerializeSingleTon.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (SerializeSingleTon) ois.readObject();
System.out.println("s1=" + s1);
System.out.println("s2=" + s2);
System.out.println(s1 == s2);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
列印結果:
s1=patternsDesign.seriableSingleTon.SerializeSingleTon@27d6c5e0
s2=patternsDesign.seriableSingleTon.SerializeSingleTon@29453f44
false
可以發現,序列化後的類出現了2個不一樣的例項!
解決方法
在類裡面新增方法private Object readResolve() { return singleTon; }
package patternsDesign.seriableSingleTon;
import java.io.Serializable;
public class SerializeSingleTon implements Serializable {
private static SerializeSingleTon singleTon = null;
private static final long serialVersionUID = -3737338076212523007L;
//1. 私有構造器
private SerializeSingleTon() {
if (InnerClass.singleTon != null) {
throw new RuntimeException("不允許建立多個單例!");
}
}
//2. 靜態內部類初始化類的例項
private static final class InnerClass {
private static final SerializeSingleTon singleTon = new SerializeSingleTon();
}
//3.提供訪問的方法
public static final SerializeSingleTon getInstance() {
singleTon = InnerClass.singleTon;
return singleTon;
}
// 4. 新增readResolve()方法來解決序列化破壞單例模式的問題
private Object readResolve() {
return singleTon;
}
}
重新執行程式,觀察結果:
s1=patternsDesign.seriableSingleTon.SerializeSingleTon@29453f44
s2=patternsDesign.seriableSingleTon.SerializeSingleTon@29453f44
true
檢視JDK原始碼
檢視原始碼, 檢視ObjectStreamClass.java檔案,找到如下程式碼,在 ObjectInputStream 類readObject()的時候,呼叫瞭如下方法,先判斷是否有readReolve()方法
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
// Filter the replacement object
if (rep != null) {
if (rep.getClass().isArray()) {
filterCheck(rep.getClass(), Array.getLength(rep));
} else {
filterCheck(rep.getClass(), -1);
}
}
handles.setObject(passHandle, obj = rep);
}
}
有的話通過反射呼叫了readResolve()方法。
Object invokeReadResolve(Object obj)
throws IOException, UnsupportedOperationException
{
requireInitialized();
if (readResolveMethod != null) {
try {
return readResolveMethod.invoke(obj, (Object[]) null);
} catch (InvocationTargetException ex) {
Throwable th = ex.getTargetException();
if (th instanceof ObjectStreamException) {
throw (ObjectStreamException) th;
} else {
throwMiscException(th);
throw new InternalError(th); // never reached
}
} catch (IllegalAccessException ex) {
// should not occur, as access checks have been suppressed
throw new InternalError(ex);
}
} else {
throw new UnsupportedOperationException();
}
}
如果該方法有返回的話,就把該例項給儲存下來,通過分析JDK原始碼, 可以知道readResolve()方法能夠解決序列化破壞單例模式的問題,實際上是建立了兩個例項,而新建立的物件沒有被返回。