轉載請註明原文地址:http://www.javashuo.com/article/p-xfppzqug-dv.htmlhtml
原理:經過一個靜態內部類定義一個靜態變量來持有當前類實例,在類加載時就建立好,在使用時獲取。java
缺點:沒法作到延遲建立對象,在類加載時進行建立會致使初始化時間變長。多線程
public class SingletonInner { private static class Holder { private static SingletonInner singleton = new SingletonInner(); } private SingletonInner(){} public static SingletonInner getSingleton(){ return Holder.singleton; }
原理:建立好一個靜態變量,每次要用時直接返回。併發
缺點:沒法作到延遲建立對象,在類加載時進行建立會致使初始化時間變長。函數
public class SingletonHungry { private static SingletonHungry instance = new SingletonHungry(); private SingletonHungry() { } public static SingletonHungry getInstance() { return instance; }
原理:延遲建立,在第一次用時才建立,以後每次用到就返回建立好的。性能
缺點:因爲synchronized的存在,多線程時效率很低。this
public class SingletonLazy { private static volatile SingletonLazy instance; private SingletonLazy() { } public static synchronized SingletonLazy getInstance() { if (instance == null) { instance = new SingletonLazy(); } return instance; }
原理:在getSingleton()方法中,進行兩次null檢查。這樣能夠極大提高併發度,進而提高性能。spa
public class Singleton { private static volatile Singleton singleton = null;//使用valatile,使該變量能被全部線程可見 private Singleton(){} public static Singleton getSingleton(){ if(singleton == null){ synchronized (Singleton.class){//初始化時,加鎖建立 if(singleton == null){//爲避免在加鎖過程當中被其餘線程建立了,再做一次非空校驗 singleton = new Singleton(); } } } return singleton; }
一、反序列化對象時會破環單例線程
反序列化對象時不會調用getXX()方法,因而繞過了確保單例的邏輯,直接生成了一個新的對象,破環了單例。code
解決辦法是:重寫類的反序列化方法,在反序列化方法中返回單例而不是建立一個新的對象。
public class Singleton implements java.io.Serializable { public static Singleton INSTANCE = new Singleton(); protected Singleton() { } //反序列時直接返回當前INSTANCE private Object readResolve() { return INSTANCE; } }
二、在代碼中經過反射機制,直接調用類的私有構造函數建立新的對象,會破環單例
解決辦法是:維護一個volatile的標誌變量在第一次建立實例時置爲false;重寫構造函數,根據標誌變量決定是否容許建立。
private static volatile boolean flag = true; private Singleton(){ if(flag){ flag = false; //第一次建立時,改變標誌 }else{ throw new RuntimeException("The instance already exists !"); }
原理:定義枚舉類型,裏面只維護一個實例,以此保證單例。每次取用時都從枚舉中取,而不會取到其餘實例。
public enum SingletonEnum { INSTANCE; private String name; public String getName(){ return name; } public void setName(String name){ this.name = name; }
優勢:
1)使用SingletonEnum.INSTANCE
進行訪問,無需再定義getInstance方法和調用該方法。
2)JVM對枚舉實例的惟一性,避免了上面提到的反序列化和反射機制破環單例的狀況出現:每個枚舉類型和定義的枚舉變量在JVM中都是惟一的。
緣由:枚舉類型在序列化時僅僅是將枚舉對象的name屬性輸出到結果中,反序列化的時候則是經過java.lang.Enum的valueOf方法來根據名字查找枚舉對象。
同時,編譯器禁止重寫枚舉類型的writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。