單例模式:是一種建立型設計模式,目的是保證全局一個類只有一個實例對象,分爲懶漢式和餓漢式。所謂懶漢式,相似於懶加載,須要的時候纔會觸發初始化實例對象。而餓漢式正好相反,項目啓動,類加載的時候,就會建立初始化單例對象。java
前面說過單例模式以及如何破壞單例模式,咱們通常狀況儘量阻止單例模式被破壞,因而各類序列化,反射,以及克隆的手段,咱們都須要考慮進來,最終的代碼以下:設計模式
import java.io.Serializable; public class Singleton implements Serializable { private static int num = 0; // valitile禁止指令重排 private volatile static Singleton singleton; // 禁止屢次反射調用構造器 private Singleton() { synchronized (Singleton.class) { if (num == 0) { num++; } else { throw new RuntimeException("Don't use this method"); } } } public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } // 禁止序列化的時候,從新生成對象 private Object readResolve() { return singleton; } }
前面提過破壞序列化的四種方式:安全
cloneable
接口可是忽然想到一個問題,通常都說枚舉的方式實現單例比較好,較爲推薦。真的是這樣麼?這樣真的是安全的麼?ide
那咱們就試試,看看各類手段,能不能破壞它的單例。首先咱們來寫一個單例枚舉類:函數
public enum SingletonEnum { INSTANCE; public SingletonEnum getInstance(){ return INSTANCE; } }
在命令行執行如下的命令看上面的枚舉類編譯以後究竟是什麼東西?學習
javac SingletonEnum.java javap SingletonEnum
public final class singleton.SingletonEnum extends java.lang.Enum<singleton.SingletonEnum> { public static final singleton.SingletonEnum INSTANCE; public static singleton.SingletonEnum[] values(); public static singleton.SingletonEnum valueOf(java.lang.String); public singleton.SingletonEnum getInstance(); static {}; }
能夠看出,實際上,編譯後的代碼是繼承於Enum
類的,而且是泛型。用final
修飾,其實也是類,那就是不能夠被繼承緣由。並且INSTANCE
也是final
修飾的,也是不可變的。可是這樣看,上面的都是public
方法。那構造方法呢?沒有被重寫成爲private
麼?ui
要是沒有重寫的話,那就很容易破壞單例啊!咱們使用javap -p SingletonEnum
看看結果:this
能夠看出確實構造函數已經被私有化,那麼外部就不能直接調用到構造方法了。那其餘方法呢?咱們試試放射調用構造器:命令行
import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public class SingletonTests { public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException { SingletonEnum singleton1 = SingletonEnum.INSTANCE; SingletonEnum singleton2 = SingletonEnum.INSTANCE; System.out.println(singleton1.hashCode()); System.out.println(singleton2.hashCode()); Constructor<SingletonEnum> constructor = null; constructor = SingletonEnum.class.getDeclaredConstructor(); constructor.setAccessible(true); SingletonEnum singleton3 = constructor.newInstance(); System.out.println(singleton1 == singleton3); } }
執行結果以下:設計
692404036 692404036 Exception in thread "main" java.lang.NoSuchMethodException: singleton.SingletonEnum.<init>() at java.lang.Class.getConstructor0(Class.java:3082) at java.lang.Class.getDeclaredConstructor(Class.java:2178) at singleton.SingletonTests.main(SingletonTests.java:15)
咦,怎麼回事?反射失敗了???
看起來報錯是getDeclaredConstructor()
失敗了,那咱們看看到底有哪些構造器:
public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException { Constructor<SingletonEnum>[] constructor = null; constructor = (Constructor<SingletonEnum>[]) SingletonEnum.class.getDeclaredConstructors(); for(Constructor<SingletonEnum> singletonEnumConstructor:constructor){ System.out.println(singletonEnumConstructor); } }
執行結果以下,發現只有一個構造器,裏面參數是String
和int
,因此啊,反射調用無參數構造器確定也是如此。
private singleton.SingletonEnum(java.lang.String,int)
畢竟它是繼承於Enum
的,那我猜測它大概也只有這個方法,驗證如下,打開源碼:
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable { private final String name; public final String name() { return name; } private final int ordinal; public final int ordinal() { return ordinal; } protected Enum(String name, int ordinal) { this.name = name; this.ordinal = ordinal; }
能夠看出,這裏面只有兩個屬性:name
和ordinal
,構造器被重寫了,正是String
和int
,驗證了咱們的猜測,也就是沒有辦法使用無參數構造器來構造出破壞單例的對象。那要是咱們使用有參數構造呢?試試!!!
import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public class SingletonTests { public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException { SingletonEnum singleton1 = SingletonEnum.INSTANCE; SingletonEnum singleton2 = SingletonEnum.INSTANCE; System.out.println(singleton1.hashCode()); System.out.println(singleton2.hashCode()); Constructor<SingletonEnum> constructor = null; constructor = SingletonEnum.class.getDeclaredConstructor(String.class,int.class);//其父類的構造器 constructor.setAccessible(true); SingletonEnum singleton3 = constructor.newInstance("INSTANCE",0); System.out.println(singleton1 == singleton3); } }
結果呢?仍是同樣的報錯,這是什麼東東?
692404036 692404036 Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects at java.lang.reflect.Constructor.newInstance(Constructor.java:417) at singleton.SingletonTests.main(SingletonTests.java:18)
看起來意思是不能反射建立enum對象,啥?這錯誤一看,就是Constructor.newInstance()
417行拋出來的,咱們看看:
@CallerSensitive public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<?> caller = Reflection.getCallerClass(); checkAccess(caller, clazz, null, modifiers); } } // 限制枚舉類型 if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects"); ConstructorAccessor ca = constructorAccessor; // read volatile if (ca == null) { ca = acquireConstructorAccessor(); } @SuppressWarnings("unchecked") T inst = (T) ca.newInstance(initargs); return inst; }
原來反射的源代碼中,枚舉類型的已經被限制了,一旦調用就會拋出異常,那這條路走不通了,也就證實了反射沒法破壞枚舉的單例。new
對象更是行不通了。
那clone
呢?打開Enum
的源碼咱們裏面就斷了這個念頭,這裏面的clone()
方法,已經被final
修飾了,不能被子類重寫,一調用就拋出異常。因此clone
這條路也不可能破壞枚舉的單例模式。
protected final Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); }
那序列化呢?若是咱們序列化以後,再反序列化,會出現什麼狀況?
import java.io.*; import java.lang.reflect.InvocationTargetException; public class SingletonTests { public static void main(String[] args) throws Exception, InvocationTargetException, InstantiationException, NoSuchMethodException { SingletonEnum singleton1 = SingletonEnum.getInstance(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("file")); objectOutputStream.writeObject(singleton1); File file = new File("file"); ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file)); SingletonEnum singleton2 = (SingletonEnum) objectInputStream.readObject(); System.out.println(singleton1.hashCode()); System.out.println(singleton2.hashCode()); } }
上面的代碼執行以後,結果以下:
1627674070 1627674070
說明序列化反序列化回來以後,實際上是同一個對象!!!因此沒法破壞單例模式。爲何呢?咱們來分析一下源碼!!!
先看看序列化的時候,實際上調用的是ObjectOutputStream.writeObject(Object obj)
writerObject()Object obj
方法裏面調用了writeObject0(obj,false)
,writeObject0(obj,false)
裏面看到枚舉類型的序列化寫入:
writeEnum(Enum<?>)
裏面是怎麼序列化的呢?
private void writeEnum(Enum<?> en, ObjectStreamClass desc, boolean unshared) throws IOException { // 標識是枚舉類型 bout.writeByte(TC_ENUM); ObjectStreamClass sdesc = desc.getSuperDesc(); // 類型描述 writeClassDesc((sdesc.forClass() == Enum.class) ? desc : sdesc, false); handles.assign(unshared ? null : en); // 將名字寫入name() writeString(en.name(), false); }
看起來序列化的時候,是用名字寫入序列化流中,那反序列化的時候呢?是怎麼操做的呢?
public final Object readObject() throws IOException, ClassNotFoundException { return readObject(Object.class); }
裏面調用的是另一個readObject()
方法,readObject()
方法實際上是調用了readObject0(type,false)
。
看到反序列化的時候,枚舉類型的時候,是怎麼實現的呢?裏面有一個readEnum()
:
咱們來看看readEnum()
,裏面其實裏面是先讀取了名字name
,再經過名字Enum.valueOf()
獲取枚舉。
因此上面沒有使用反射,仍是獲取了以前的對象,綜上所述,枚舉的序列化和反序列化並不會影響單例模式。
通過上面一頓分析,枚舉不能夠直接調用構造函數,不能夠反射破壞單例模式,由於內部實現阻止了,實現clone
接口也不能夠,這個方法已經設置爲final
。序列化和反序列化的時候,內部沒有使用反射去實現,而是查找以前的對象,直接返回,因此仍是同一個對象。
這樣一來,怪不得《effective java》裏面推薦這個寫法,既簡潔,還可以防止各類破壞,還有不用的理由麼?
【做者簡介】:
秦懷,公衆號【秦懷雜貨店】做者,技術之路不在一時,山高水長,縱使緩慢,馳而不息。這個世界但願一切都很快,更快,可是我但願本身能走好每一步,寫好每一篇文章,期待和大家一塊兒交流。
此文章僅表明本身(本菜鳥)學習積累記錄,或者學習筆記,若有侵權,請聯繫做者覈實刪除。人無完人,文章也同樣,文筆稚嫩,在下不才,勿噴,若是有錯誤之處,還望指出,感激涕零~