保證一個了類僅有一個實例,並提供一個訪問它的全局訪問點。java
優勢:數據庫
缺點:安全
類初始化時,會當即加載該對象,線程安全,效率高。多線程
/** * @Author 劉翊揚 * @Version 1.0 */ public class SingletonHungry { private static SingletonHungry instance = new SingletonHungry(); private SingletonHungry() {} public static SingletonHungry getInstance() { return instance; } }
驗證:ide
public class Main { public static void main(String[] args) { SingletonHungry instance1 = SingletonHungry.getInstance(); SingletonHungry instance2 = SingletonHungry.getInstance(); System.out.println(instance1 == instance2); // 結果是true } }
優勢:僅實例化一次,線程是安全的。獲取實例的速度快
缺點:類加載的時候當即實例化對象,可能實例化的對象不會被使用,形成內存的浪費。函數
/** * @author 劉翊揚 */ public class HungrySingleton2 { private static HungrySingleton2 instance = null; private HungrySingleton2() {} static { instance = new HungrySingleton2(); } private HungrySingleton2() {} public static HungrySingleton2 getInstance() { return instance; } }
public class SingletonLazy { private static SingletonLazy instance; private SingletonLazy() {} public static SingletonLazy getInstance() { if (instance == null) { instance = new SingletonLazy(); } return instance; } }
優勢:在使用的時候,建立對象,節省系統資源
缺點:性能
/** * @Author 劉翊揚 * @Version 1.0 */ public class SingletonDemo03 { private SingletonDemo03() {} public static class SingletonClassInstance { private static final SingletonDemo03 instance = new SingletonDemo03(); } public static SingletonDemo03 getInstance() { return SingletonClassInstance.instance; } }
優點:兼顧了懶漢模式的內存優化(使用時才初始化)以及餓漢模式的安全性(不會被反射入侵)。優化
劣勢:須要兩個類去作到這一點,雖然不會建立靜態內部類的對象,可是其 Class 對象仍是會被建立,並且是屬於永久帶的對象。網站
枚舉自己就是單例的,通常在項目中定義常量。
例如:this
/** * @Author 劉翊揚 * @Version 1.0 */ public enum ResultCode { SUCCESS(200, "SUCCESS"), ERROR(500, "ERROR"); private Integer code; private String msg; ResultCode(Integer code, String msg) { this.code = code; this.msg = msg; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } }
/** * @Author 劉翊揚 * @Version 1.0 */ public class User { private User() {} public static User getInstance() { return SingletonDemo04.INSTANCE.getInstance(); } private static enum SingletonDemo04 { INSTANCE; // 枚舉元素爲單例 private User user; SingletonDemo04() { user = new User(); } public User getInstance() { return user; } } }
public class LazySingletonDemo1 { private static LazySingletonDemo1 instance = null; public static LazySingletonDemo1 getInstance() { if (instance == null) { synchronized (LazySingletonDemo1.class) { if (instance == null) { instance = new LazySingletonDemo1(); } } } return instance; } }
這裏使用雙重檢測,是爲了防止,當實例存在的時候,不在走同步鎖,減小使用鎖帶來的性能的消耗。
答案是:不能
破壞單例的兩種方式:
- 反射
- 反序列化
經過反射是能夠破壞單例的,例如使用內部類實現的單例。經過反射獲取其默認的構造函數,而後使默認構造函數可訪問,就能夠建立新的對象了。
/** * @Author 劉翊揚 * @Version 1.0 */ public class ReflectionDestroySingleton { public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { SingletonLazy instance = SingletonLazy.getInstance(); Class aClass = SingletonLazy.class; // 獲取默認的構造方法 Constructor<SingletonLazy> declaredConstructor = aClass.getDeclaredConstructor(); // 使默認構造方法可訪問 declaredConstructor.setAccessible(true); // 建立對象 SingletonLazy instance2 = declaredConstructor.newInstance(); System.out.println(instance == instance2); // 結果是:false } }
怎麼阻止???
能夠增長一個標誌位,用來判斷構造函數是否被調用了。
public class SingletonLazy { // 標誌位 private static Boolean isNew = false; private static SingletonLazy instance; private SingletonLazy() { synchronized (SingletonLazy.class) { if (!isNew) { isNew = true; } else { throw new RuntimeException("單例模式被破壞!"); } } } public static SingletonLazy getInstance() { if (instance == null) { instance = new SingletonLazy(); } return instance; } }
再次運行:
注意:
增長標誌位的確能阻止單例的破壞,可是這個代碼有一個BUG,那就是若是單例是先用的反射建立的,那若是你再用正常的方法getInstance()獲取單例,就會報錯。由於此時標誌位已經標誌構造函數被調用過了。這種寫法除非你能保證getInstance先於反射執行。
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { Class aClass = SingletonLazy.class; // 獲取默認的構造方法 Constructor<SingletonLazy> declaredConstructor = aClass.getDeclaredConstructor(); // 使默認構造方法可訪問 declaredConstructor.setAccessible(true); // 建立對象 SingletonLazy instance2 = declaredConstructor.newInstance(); System.out.println("反射實例:" + instance2); // 再次調用 SingletonLazy instance = SingletonLazy.getInstance(); System.out.println(instance == instance2); // 結果是:false }
結果:
SingletonLazy要實現Serializable接口
public static void main(String[] args) throws Exception { //序列化 SingletonLazy instance1 = SingletonLazy.getInstance(); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("tempfile.txt")); out.writeObject(SingletonLazy.getInstance()); File file = new File("tempfile.txt"); ObjectInputStream in = new ObjectInputStream(new FileInputStream(file)); //調用readObject()反序列化 SingletonLazy instance2 = (SingletonLazy) in.readObject(); System.out.println(instance1 == instance2); // 結果是:false }
原理解釋:
反序列化爲何能生成新的實例,必須從源碼看起。這裏分析readObject()裏面的調用源碼。會發現readObject()方法後進入了readObject0(false)方法。
public final Object readObject() throws IOException, ClassNotFoundException { if (enableOverride) { return readObjectOverride(); } // if nested read, passHandle contains handle of enclosing object int outerHandle = passHandle; try { Object obj = readObject0(false); //經過debug會發現進入此方法 handles.markDependency(outerHandle, passHandle); ClassNotFoundException ex = handles.lookupException(passHandle); if (ex != null) { throw ex; } if (depth == 0) { vlist.doCallbacks(); } return obj; } finally { passHandle = outerHandle; if (closed && depth == 0) { clear(); } } }
分析readObject0方法,經過debug進入了readOrdinaryObject()方法。
private Object readObject0(Class<?> type, boolean unshared) throws IOException { boolean oldMode = bin.getBlockDataMode(); if (oldMode) { int remain = bin.currentBlockRemaining(); if (remain > 0) { throw new OptionalDataException(remain); } else if (defaultDataEnd) { /* * Fix for 4360508: stream is currently at the end of a field * value block written via default serialization; since there * is no terminating TC_ENDBLOCKDATA tag, simulate * end-of-custom-data behavior explicitly. */ throw new OptionalDataException(true); } bin.setBlockDataMode(false); } byte tc; while ((tc = bin.peekByte()) == TC_RESET) { bin.readByte(); handleReset(); } depth++; totalObjectRefs++; try { switch (tc) { .... 省略部分源碼 case TC_OBJECT: if (type == String.class) { throw new ClassCastException("Cannot cast an object to java.lang.String"); } return checkResolve(readOrdinaryObject(unshared)); // 經過debug發現進入到了readOrdinaryObject()方法。 .... 省略部分源碼 } } finally { depth--; bin.setBlockDataMode(oldMode); } }
經過分析,readOrdinaryObject()中有兩處關鍵代碼,其中關鍵代碼1中的關鍵語句爲:
此處代碼是經過描述對象desc,先判斷類是否能夠實例化,若是能夠實例化,則執行desc.newInstance()經過反射實例化類,不然返回null。
obj = desc.isInstantiable() ? desc.newInstance() : null;
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); 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; }
經過斷點調試,發現其調用了desc.newInstance()方法。
咱們知道調用newInstance()方法,必定會走類的無參構造方法,可是上面經過debug咱們發現,cons的類型是Object類型,因此,這裏面應該是調用類Object的無參構造方法,而不是SingletonLazy類的無參構造
那麼怎麼改造呢????
繼續debug調試:查看readOrdinaryObject()方法
發現,desc.hasReadResolveMethod()這個方法返回的false,因此致使沒有執行if條件下面的語句。
desc.hasReadResolveMethod() // 從方法名能夠看到,這個方法的名字是檢查desc這個(SingleLazy)對象有沒有readResolve()方法。
咱們如今阻止破壞單例,應該只須要在SingleLazy類中,實現本身的readResolve()方法便可。
public Object readResolve() { return instance; }
如今咱們在看看運行的結果:爲true
大功告成。。。。