單例模式(Singleton Patten):確保某一個類只有一個實例,並且自行實例化並向整個系統提供這個實例(Ensure a class has only one instance, and provide a global point of access to it)。程序員
Singleton類稱爲單例類,經過使用private的構造函數確保了在一個應用中只產生一個實例,而且是自行實例化的(在Singleton中本身使用new Singleton())。安全
餓漢式:在類加載時就建立對象實例,而無論實際是否須要建立。多線程
public class Singleton { private static final Singleton singleton = new Singleton(); private Singleton() { } public static Singleton getInstance() { return singleton; } }
懶漢式:只有調用getInstance的時候,才實例化對象。ide
public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }
問題:函數
方案1:源碼分析
在getInstance()方法上加synchronized關鍵字。性能
public class Singleton { private static Singleton singleton; private Singleton() { } public static synchronized Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }
問題:只有在單例初始化的時候咱們才須要保證線程安全,其餘時候方法上的synchronized關鍵字只會下降性能。測試
方案2:flex
只在單例初始化的時候加synchronized關鍵字。優化
public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { singleton = new Singleton(); } } return singleton; } }
問題:仍是有可能會有多個進程同時經過(singleton == null)的條件檢查,進而建立多個實例。
方案3:
public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance() { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } return singleton; } }
問題:和方案1相似,原本只是想讓new這個操做並行,如今只要是進入getInstance()的線程都得同步,影響性能。
方案4:
雙重檢查加鎖(Double-Check Lock)。
public class Singleton { private static Singleton singleton = null; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { //若是被同步的線程中,有一個線程建立了對象,那麼別的線程就不用再建立了 if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
問題:DCL失效。
DCL失效主要在於singleton = new Singleton()
這句,這並不是是一個原子操做,事實上在JVM中這句話大概作了下面 3 件事情:
可是在 JVM 的即時編譯器中存在指令重排序的優化。也就是說上面的第二步和第三步的順序是不能保證的,最終的執行順序多是 1-2-3 也多是 1-3-2。若是是後者,則在3執行完畢、2未執行以前,若是另外一個線程搶佔了鎖,這時 instance 已是非 null 了(但卻沒有初始化),因此該線程會直接返回 instance,而後使用,而後瓜熟蒂落地報錯。
方案5(最終版):
加volatile。
public class Singleton { private volatile static Singleton singleton = null; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
使用 volatile 有兩個做用:
這個變量不會在多個線程中保存複本,而是直接從內存讀取。
老版《Effective Java》中推薦的方式:
public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton() { } public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }
上面這種方式,仍然使用JVM自己的機制保證了線程安全問題:
枚舉實現:
public enum Singleton { INSTANCE; public void doSomething() { } }
默認枚舉實例的建立是線程安全的,因此不須要擔憂線程安全的問題。可是在枚舉中的其餘方法的線程安全由程序員本身負責。還有防止上面的經過反射機制調用私用構造器。
這個版本基本上消除了絕大多數的問題,代碼也很是簡單,是新版的《Effective Java》中推薦的模式。
單例實現採用方案5,序列化攻擊代碼以下:
public class Main { public static void main(String[] args) throws IOException, ClassNotFoundException { //序列化方式破壞單例 測試 serializeDestroyMethod(); } private static void serializeDestroyMethod() throws IOException, ClassNotFoundException { Singleton singleton; Singleton singletonNew; singleton = Singleton.getInstance(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(singleton); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); singletonNew = (Singleton) ois.readObject(); System.out.println(singleton == singletonNew); } }
打印結果爲:false。在單例類中添加一個方法 readResolve():
public class Singleton implements Serializable { private volatile static Singleton singleton = null; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } private Object readResolve() { return singleton; } }
再執行攻擊代碼,打印結果爲:true。
反序列化攻擊源碼分析:
//默認狀況下 該方法經過反射建立一個新對象並返回 private Object readOrdinaryObject(boolean unshared) throws IOException { if (bin.readByte() != TC_OBJECT) { throw new InternalError(); } ObjectStreamClass desc = readClassDesc(false); desc.checkDeserialize(); Class<?> cl = desc.forClass(); if (cl == String.class || cl == Class.class || cl == ObjectStreamClass.class) { throw new InvalidClassException("invalid class descriptor"); } Object obj; try { obj = desc.isInstantiable() ? desc.newInstance() : null; } catch (Exception ex) { throw (IOException) new InvalidClassException( desc.forClass().getName(), "unable to create instance").initCause(ex); } passHandle = handles.assign(unshared ? unsharedMarker : obj); ClassNotFoundException resolveEx = desc.getResolveException(); if (resolveEx != null) { handles.markException(passHandle, resolveEx); } if (desc.isExternalizable()) { readExternalData((Externalizable) obj, desc); } else { readSerialData(obj, desc); } handles.finish(passHandle); //通過上面的代碼,新對象已經被new出來了 //下面hasReadResolveMethod()這個方法很關鍵。若是該類存在readResolve()方法,就調用該方法返回的實例替換掉新建立的對象。若是不存在就直接把new出來的對象返回出去。 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); } } return obj; }
... /** * 返回該類是否有readResolve方法 */ boolean hasReadResolveMethod() { requireInitialized(); return (readResolveMethod != null); }
因此在單例類中添加方法 readResolve(),就能夠防範反序列化攻擊。
單例實現依然採用方案5,反射攻擊代碼以下:
public class Main { public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { reflexDestroyMethod(); } private static void reflexDestroyMethod() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Class objectClass = Singleton.class; Constructor constructor = objectClass.getDeclaredConstructor(); constructor.setAccessible(true); Singleton singleton = Singleton.getInstance(); Singleton singletonNew = (Singleton) constructor.newInstance(); System.out.println(singleton == singletonNew); } }
打印結果爲:false。
使用枚舉實現單例後,執行反射攻擊報錯以下:
緣由是Singleton.class.getDeclaredConstructors()獲取全部構造器,會發現並無咱們所設置的無參構造器,只有一個參數爲(String.class,int.class)構造器。看下Enum源碼就明白,這兩個參數是name和ordinal兩個屬性:
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; } //餘下省略
枚舉Enum是個抽象類,一旦一個類聲明爲枚舉,實際上就是繼承了Enum,因此就會有(String.class,int.class)的構造器。既然無參構造方法找不到,那咱們就使用父類Enum的構造器,看看是什麼狀況:
public class Main { public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { //反射方式破壞單例 測試 reflexDestroyMethod(); } private static void reflexDestroyMethod() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Class objectClass = SingletonE.class; Constructor constructor = objectClass.getDeclaredConstructor(String.class, int.class); constructor.setAccessible(true); SingletonE singleton = SingletonE.INSTANCE; SingletonE singletonNew = (SingletonE) constructor.newInstance("test", 1); System.out.println(singleton == singletonNew); } }
執行結果以下:
說是不能反射建立枚舉對象,newInstance()方法源碼以下:
@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; }
由上文源碼可知,反射在經過newInstance建立對象時,會檢查該類是否ENUM修飾,若是是則拋出異常,反射失敗。
單元素的枚舉類型已經成爲實現Singleton的最佳方法 —— 《Effective Java》