設計模式-建立者模式-單例模式(存在的問題以及解決方案)
阿新 • • 發佈:2022-03-10
一、 存在的問題
1. 問題演示
破壞單例模式:
使上面定義的單例類(Singleton)可以建立多個物件,列舉方式除外。有兩種方式,分別是序列化和反射。
- 序列化反序列化
Singleton類:
必須要實現Serializable
一個物件序列化的介面,一個類只有實現了Serializable介面,它的物件才能被序列化。
package com.mangoubiubiu.designpattern.single; import java.io.Serializable; /** * 餓漢式 * 靜態變數建立類的物件 */ public classSingleEH implements Serializable { private SingleEH(){} //在成員變數建立物件 private static SingleEH singleEH =new SingleEH(); //提供對外訪問的靜態方法 public static SingleEH getSingleEH(){ return singleEH; } }
package com.mangoubiubiu.designpattern.single; import java.io.FileInputStream;import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class DestorySingle { public static void main(String[] args) throws Exception{ writeSingle(); readSingle(); } //從檔案中讀資料 public static void readSingle() throwsException{ SingleEH singleEH=SingleEH.getSingleEH(); ObjectInputStream inputStream =new ObjectInputStream(new FileInputStream("D:\\single.txt")); SingleEH singleObject = (SingleEH)inputStream.readObject(); System.out.println(singleEH); System.out.println(singleObject); inputStream.close(); } //向檔案中寫入資料 public static void writeSingle() throws Exception{ SingleEH singleEH=SingleEH.getSingleEH(); ObjectOutputStream outputStream =new ObjectOutputStream(new FileOutputStream("D:\\single.txt")); outputStream.writeObject(singleEH); outputStream.close(); } }
執行結果
不是同一物件,說明序列化和反序列化破壞了單列模式
反射
Singleton類:
package com.mangoubiubiu.designpattern.single; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Constructor; public class DestorySingle { public static void main(String[] args) throws Exception{ //獲取SingleEH的位元組碼物件 Class clazz=SingleEH.class; //2,獲取無參構造方法 Constructor constructor=clazz.getDeclaredConstructor(); //3,取消訪問檢查 constructor.setAccessible(true); //建立物件 SingleEH clazzSingleEH=(SingleEH)constructor.newInstance(); SingleEH singleEH=SingleEH.getSingleEH(); System.out.println(singleEH); System.out.println(clazzSingleEH); } }
執行結果
不是同一物件,說明反射破壞了單列模式
二、 解決方案
序列化、反序列方式破壞單例模式的解決方法
在Singleton類中新增readResolve()方法,在反序列化時被反射呼叫,如果定義了這個方法,就返回這個方法的值,如果沒有定義,則返回新new出來的物件。
package com.mangoubiubiu.designpattern.single; import java.io.Serializable; /** * 餓漢式 * 靜態變數建立類的物件 */ public class SingleEH implements Serializable { private SingleEH(){} //在成員變數建立物件 private static SingleEH singleEH =new SingleEH(); //提供對外訪問的靜態方法 public static SingleEH getSingleEH(){ return singleEH; } /** * 下面是為了解決序列化反序列化破解單例模式 */ private Object readResolve() { return singleEH; } }
package com.mangoubiubiu.designpattern.single; import java.io.*; import java.lang.reflect.Constructor; public class DestorySingle { public static void main(String[] args) throws Exception { // writeSingle(); readSingle(); } public static void reflectSingle()throws Exception{ //獲取SingleEH的位元組碼物件 Class clazz=SingleEH.class; //2,獲取無參構造方法 Constructor constructor=clazz.getDeclaredConstructor(); //3,取消訪問檢查 constructor.setAccessible(true); //建立物件 SingleEH clazzSingleEH=(SingleEH)constructor.newInstance(); SingleEH singleEH=SingleEH.getSingleEH(); System.out.println(singleEH); System.out.println(clazzSingleEH); } //從檔案中讀資料 public static void readSingle() throws Exception{ SingleEH singleEH=SingleEH.getSingleEH(); ObjectInputStream inputStream =new ObjectInputStream(new FileInputStream("D:\\single.txt")); SingleEH singleObject = (SingleEH)inputStream.readObject(); System.out.println(singleEH); System.out.println(singleObject); inputStream.close(); } //向檔案中寫入資料 public static void writeSingle() throws Exception{ File file =new File("D:\\single.txt"); if(file.exists()){ System.out.println("檔案已存在---準備刪除檔案"); file.delete(); } SingleEH singleEH=SingleEH.getSingleEH(); ObjectOutputStream outputStream =new ObjectOutputStream(new FileOutputStream("D:\\single.txt")); outputStream.writeObject(singleEH); outputStream.close(); } }
發現為同一物件
原始碼解析:
ObjectInputStream類
public final Object readObject() throws IOException, ClassNotFoundException{ ... // if nested read, passHandle contains handle of enclosing object int outerHandle = passHandle; try { Object obj = readObject0(false);//重點檢視readObject0方法 ..... } private Object readObject0(boolean unshared) throws IOException { ... try { switch (tc) { ... case TC_OBJECT: return checkResolve(readOrdinaryObject(unshared));//重點檢視readOrdinaryObject方法 ... } } finally { depth--; bin.setBlockDataMode(oldMode); } } private Object readOrdinaryObject(boolean unshared) throws IOException { ... //isInstantiable 返回true,執行 desc.newInstance(),通過反射建立新的單例類, obj = desc.isInstantiable() ? desc.newInstance() : null; ... // 在Singleton類中新增 readResolve 方法後 desc.hasReadResolveMethod() 方法執行結果為true if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { // 通過反射呼叫 Singleton 類中的 readResolve 方法,將返回值賦值給rep變數 // 這樣多次呼叫ObjectInputStream類中的readObject方法,繼而就會呼叫我們定義的readResolve方法,所以返回的是同一個物件。 Object rep = desc.invokeReadResolve(obj); ... } return obj; }
反射方式破解單例的解決方法
public class Singleton { //私有構造方法 private Singleton() { /* 反射破解單例模式需要新增的程式碼 */ if(instance != null) { throw new RuntimeException(); } } private static volatile Singleton instance; //對外提供靜態方法獲取該物件 public static Singleton getInstance() { if(instance != null) { return instance; } synchronized (Singleton.class) { if(instance != null) { return instance; } instance = new Singleton(); return instance; } } }
說明:
這種方式比較好理解。當通過反射方式呼叫構造方法進行建立建立時,直接拋異常。不執行此中操作。
JDK原始碼解析-Runtime類
Runtime類就是使用的單例設計模式。
- 通過原始碼檢視使用的是哪兒種單例模式
public class Runtime { private static Runtime currentRuntime = new Runtime(); /** * Returns the runtime object associated with the current Java application. * Most of the methods of class <code>Runtime</code> are instance * methods and must be invoked with respect to the current runtime object. * * @return the <code>Runtime</code> object associated with the current * Java application. */ public static Runtime getRuntime() { return currentRuntime; } /** Don't let anyone else instantiate this class */ private Runtime() {} ... }
- 使用Runtime類中的方法
public class RuntimeDemo { public static void main(String[] args) throws IOException { //獲取Runtime類物件 Runtime runtime = Runtime.getRuntime(); //返回 Java 虛擬機器中的記憶體總量。 System.out.println(runtime.totalMemory()); //返回 Java 虛擬機器試圖使用的最大記憶體量。 System.out.println(runtime.maxMemory()); //建立一個新的程序執行指定的字串命令,返回程序物件 Process process = runtime.exec("ipconfig"); //獲取命令執行後的結果,通過輸入流獲取 InputStream inputStream = process.getInputStream(); byte[] arr = new byte[1024 * 1024* 100]; int b = inputStream.read(arr); System.out.println(new String(arr,0,b,"gbk")); } }