單例模式---對於整個系統只須要一個實體就能完成工做的狀況下,咱們系統只須要一個實體而且保證只有一個實例,避免形成資源浪費java
1.懶漢ios
懶漢模式是在須要用到該實例的時候才進行實例化 安全
優勢:節約資源,在須要用到該實例的時候才初始化多線程
缺點:線程非安全,併發訪問狀況下,有可能屢次實例化,而且每次實例化都覆蓋上一次的實例併發
public class Singleton { private static Singleton SINGLETON; private Singleton(){} public static Singleton getInstance(){ if(Singleton.SINGLETON == null); Singleton.SINGLETON = new Singleton(); return SINGLETON; } }
2.餓漢ide
餓漢單例模式在類加載的時候就實例化性能
優勢:安全,不存在併發建立多實例問題優化
缺點:容易形成資源浪費,一直佔用着資源且沒法回收ui
public class Singleton { private static final Singleton SINGLETON = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return SINGLETON; } }
3.懶漢模式(方法加鎖)this
這種模式在獲取實例的時候添加synchronize同步鎖能避免多併發狀況下形成建立多實例問題
優勢:具備懶漢模式的節約資源優勢,且方法加鎖狀況下避免了多併發建立屢次實例的狀況
肯定:方法鎖消耗性能比較大,必須是第一訪問完整個方法纔到第二次訪問進入
public class Singleton { private static Singleton SINGLETON; private Singleton(){} public synchronized static Singleton getInstance(){ if(Singleton.SINGLETON == null); Singleton.SINGLETON = new Singleton(); return SINGLETON; } }
4.雙重鎖校驗(推薦)
雙重鎖校驗是優化了方發鎖的方式而來,優化啊了多併發狀況下性能低下的結果
優勢:保證了線程安全狀況下,節約資源且訪問性能高
public class Singleton { private static Singleton SINGLETON; private Singleton(){} public static Singleton getInstance(){ if(Singleton.SINGLETON == null){ synchronized (Singleton.class) { if(Singleton.SINGLETON == null) { Singleton.SINGLETON = new Singleton(); } } } return SINGLETON; } }
進入方法體以後首先判斷了實例是否存在,若是存在,則直接返回實例,不然加鎖執行多一次判斷,若是爲null再實例化。由於第一次判斷和加鎖之間,對象可能已經實例化,因此加鎖以後再判斷一次,避免屢次建立。可是這種方式還有點缺陷,synchronized關鍵字能夠保證多線程狀況下同步問題,若是是多核計算機(如今絕大部分都是多核計算機)狀況下,還會有一個指令重排的問題因此咱們須要用volatile 來修飾SINGLETON,最後改形成下面代碼
public class Singleton { private volatile static Singleton SINGLETON; private Singleton(){} public static Singleton getInstance(){ if(Singleton.SINGLETON == null){ synchronized (Singleton.class) { if(Singleton.SINGLETON == null) { Singleton.SINGLETON = new Singleton(); } } } return SINGLETON; } }
5.靜態內部類
靜態內部類是在調用的時候纔會進行加載,是懶漢模式另一種實現方式
public class Singleton { private Singleton(){} public static Singleton getInstance(){ return Instance.singleton; } private static class Instance{ private static final Singleton singleton = new Singleton(); } }
6.枚舉
枚舉爲最優的單例模式實現方案,由於能夠防反射暴力建立對象,也能夠避免序列化問題,下面先放了一個簡單的例子,
public enum SingletonEnum { SINGLETON; private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public static SingletonEnum getInstance(){ return SINGLETON; } }
看一下使用方式
public static void main(String[] args) { SingletonEnum.SINGLETON.setName("name1"); System.out.println(SingletonEnum.SINGLETON.getName()); }
輸出結果,因而可知 SingletonEnum.SINGLETON 時調用的都是同一個實例
下面咱們看看枚舉類型防放射暴力建立實例
咱們用以前靜態內部類的那個代碼來比較
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { // 反射獲取構造器 Constructor<Singleton> singletonConstructor = Singleton.class.getDeclaredConstructor(); // 經過構造器建立對象 Singleton singleton1 = singletonConstructor.newInstance(); // 經過咱們單例獲取實例的接口獲取實例 Singleton singleton2 = Singleton.getInstance(); // 下面結果爲false,證實是2個不同的實例,甚至都不用調用構造器的 setAccessible() 就能成功新建一個實例 System.out.println(singleton1 == singleton2); }
接下來再看看枚舉
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { // 反射獲取構造器 Constructor<SingletonEnum> singletonEnumConstructor = SingletonEnum.class.getDeclaredConstructor(); // 經過構造器建立對象 SingletonEnum singletonEnum1 = singletonEnumConstructor.newInstance(); // 獲取單例 SingletonEnum singletonEnum2 = SingletonEnum.SINGLETON; // 下面結果爲false,證實是2個不同的實例,甚至都不用調用構造器的 setAccessible() 就能成功新建一個實例 System.out.println(singletonEnum1 == singletonEnum2); }
這時候報是報了個java.lang.NoSuchMethodException,緣由是由於枚舉類型沒有無參構造
下面咱們進入debug模式能夠看到只有一個帶一個String參數和一個int參數的構造方法
因此改形成
public static void main(String[] args) throws NoSuchMethodException,
IllegalAccessException, InvocationTargetException, InstantiationException {
// 反射獲取構造器
Constructor<SingletonEnum> singletonEnumConstructor = SingletonEnum.class.getDeclaredConstructor(String.class,int.class);
// 經過構造器建立對象
SingletonEnum singletonEnum1 = singletonEnumConstructor.newInstance("",1);
// 獲取單例
SingletonEnum singletonEnum2 = SingletonEnum.SINGLETON;
// 下面結果爲false,證實是2個不同的實例,甚至都不用調用構造器的 setAccessible() 就能成功新建一個實例
System.out.println(singletonEnum1 == singletonEnum2);
}
可是改過來以後報了個 java.lang.IllegalAccessException 非法訪問異常
緣由是若是實例化的對象是個枚舉類型,就會拋出這個異常,這說明枚舉類型天生就是單例的
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; }
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
序列化與反序列化,若是咱們實體須要儲存到程序之外的存儲媒介,當再次獲取時候,這個實例並不是咱們最開始的實例
序列化的時候實體類必須實現 Serializable
public class Singleton implements Serializable{}
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException { // 經過獲取實例接口獲取實例 Singleton singleton1 = Singleton.getInstance(); // 建立輸出流而且輸出到文件 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\singleton\\singleton.txt")); oos.writeObject(singleton1); // 建立輸入流而且反序列化實例 ObjectInputStream ios = new ObjectInputStream(new FileInputStream("D:\\singleton\\singleton.txt")); Singleton singleton2 = (Singleton) ios.readObject(); oos.close(); ios.close(); System.out.println(singleton1 == singleton2); }
序列化先後的對象結果對比,不是同一個實例
再看看枚舉類型
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException { SingletonEnum singletonEnum1 = SingletonEnum.SINGLETON; // 建立輸出流而且輸出到文件 ObjectOutputStream oosE = new ObjectOutputStream(new FileOutputStream("D:\\singleton\\singletonE.txt")); oosE.writeObject(singletonEnum1); // 建立輸入流而且反序列化實例 ObjectInputStream iosE = new ObjectInputStream(new FileInputStream("D:\\singleton\\singletonE.txt")); SingletonEnum singletonEnum2 = (SingletonEnum) iosE.readObject(); oosE.close(); iosE.close(); System.out.println(singletonEnum1 == singletonEnum2); }
序列化先後的對象是一致的,沒有被破壞
因此單例的最優方案是枚舉,其餘方法都會由於反射或者序列化破壞了整個系統只有一個實例的原則,固然根據業務要求選擇一種比較合適目前開發團隊的方案也很重要