一說到單例模式,我想大家首先想到的是懶漢式、惡漢式吧!至於登記式(淘汰的模式,可忽略)。java
單例模式有如下特色:
一、單例類只能有一個實例。
二、單例類必須本身建立本身的惟一實例。
三、單例類必須給全部其餘對象提供這一實例。安全
1、懶漢式單例併發
先寫一個懶漢式的單例模式。ide
public class Singleton { private Singleton() {} private static Singleton single=null; public static Singleton getInstance() { if (single == null) { single = new Singleton(); } return single; } }
Singleton經過將構造方法限定爲private避免了其餘類經過訪問構造器進行實例化,在同一個虛擬機範圍內,Singleton的惟一實例只能經過靜態的getInstance()方法進行訪問。性能
可是上面的代碼是不考慮線程安全的狀況下,也就是說,該實例是存在線程安全的。在併發的狀況下是可能出現這種狀況,就是a線程先進入getInstance()方法在建立實例化的時候,也就是還沒建立成功,b線程也進入了getInstance()方法,這個時候a線程實例還沒建成功,b線程判斷single爲空也開始建立實例,致使會出現建立出兩個實例來。ui
解決方式有三種:spa
public static synchronized Singleton getInstance() { if (single == null) { single = new Singleton(); } return single; }
加上synchronized關鍵字,併發的時候也只能一個一個排隊進行getInstance()方法訪問。線程
public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; }
雙重檢查鎖定,這種方式會優於上面一種方式,在併發量高的狀況下,不須要排隊進getInstance()方法合理利用系統資源,性能上會優於上面一種。code
public class Singleton { private static class LazyHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return LazyHolder.INSTANCE; } }
靜態內部類實現單例模式,這種方式優於上面兩種方式,他即實現了線程安全,又省去了null的判斷,性能優於上面兩種。對象
2、餓漢式單例
public class Singleton { private Singleton() {} private static final Singleton single = new Singleton(); public static Singleton getInstance() { return single; } }
餓漢式是靜態加載的時候實,不須要擔憂線程安全問題。
3、枚舉單例模式
以上兩種方式是在不考慮放射機制和序列化機制的狀況下實現的單例模式,可是若是考慮了放射,則上面的單例就沒法作到單例類只能有一個實例這種說法了。事實上,經過Java反射機制是可以實例化構造方法爲private的類的。這也就是咱們如今須要引入的枚舉單例模式。
public enum EnumSingleton { INSTANCE; public EnumSingleton getInstance(){ return INSTANCE; } }
舉個例子就能知道上面的單例不是很安全,以雙重檢索的單例模式爲例子,我利用放射,可以建立出新的實例:
public static void main(String[] args) throws Exception { Singleton s=Singleton.getInstance(); Singleton sual=Singleton.getInstance(); Constructor<Singleton> constructor=Singleton.class.getDeclaredConstructor(); constructor.setAccessible(true); Singleton s2=constructor.newInstance(); System.out.println(s+"\n"+sual+"\n"+s2); System.out.println("正常狀況下,實例化兩個實例是否相同:"+(s==sual)); System.out.println("經過反射攻擊單例模式狀況下,實例化兩個實例是否相同:"+(s==s2)); }
結果爲:
cn.singleton.Singleton@1641e19d cn.singleton.Singleton@1641e19d cn.singleton.Singleton@677323b6 正常狀況下,實例化兩個實例是否相同:true 經過反射攻擊單例模式狀況下,實例化兩個實例是否相同:false
因而可知雙重檢索模式不是最安全的,沒法避免反射的攻擊。
咱們檢測一下枚舉的單例模式
public static void main(String[] args) throws Exception{ EnumSingleton singleton1=EnumSingleton.INSTANCE; EnumSingleton singleton2=EnumSingleton.INSTANCE; System.out.println("正常狀況下,實例化兩個實例是否相同:"+(singleton1==singleton2)); Constructor<EnumSingleton> constructor= null; constructor = EnumSingleton.class.getDeclaredConstructor(); constructor.setAccessible(true); EnumSingleton singleton3= null; singleton3 = constructor.newInstance(); System.out.println(singleton1+"\n"+singleton2+"\n"+singleton3); System.out.println("經過反射攻擊單例模式狀況下,實例化兩個實例是否相同:"+(singleton1==singleton3)); }
結果會報Exception in thread "main" java.lang.NoSuchMethodException。出現這個異常的緣由是由於EnumSingleton.class.getDeclaredConstructors()獲取全部構造器,會發現並無咱們所設置的無參構造器,只有一個參數爲(String.class,int.class)構造器,並且在反射在經過newInstance建立對象時,會檢查該類是否ENUM修飾,若是是則拋出異常,反射失敗。因此枚舉是不怕發射攻擊的。
newInstance方法源碼:
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; }