單例模式
阿新 • • 發佈:2017-05-04
實現 程序 先來 null effective 如果 ava 不同的 aps 單例模式:確保某一個類只有一個實例,而且自行實例化並向整個系統提供這個實例。
優點:
1、省略創建對象所花費的時間減少系統開銷,尤其是重量級對象。
2、減少對象的創建,減輕GC壓力。
3、設置全局訪問入口,優化資源訪問。
一、最簡單的實現
方案1:
以上兩個問題有點偏,先掩耳盜鈴不考慮。 問題3:對象在類加載時就創建了不管使用與否,創建對象消耗小還好,消耗大的話就容易造成浪費。 對於問題2的解決方案是延時加載。
方案2:
方案3:
1 public class SingletonClass { 2 private static final SingletonClass instance = new SingletonClass(); 3 4 private SingletonClass(){} 5 6 publicView Code 將SingletonClass對象聲明為final類型結合Java的類加載機制,保證了對象的唯一性。 二、延時加載 方案1乍看上去簡單可行,但會出現兩個問題: 問題1:可以通過反射再創建實例。 問題2:可以通過反序列化創建實例。static SingletonClass getInstance(){ 7 return instance; 8 } 9 10 }
以上兩個問題有點偏,先掩耳盜鈴不考慮。 問題3:對象在類加載時就創建了不管使用與否,創建對象消耗小還好,消耗大的話就容易造成浪費。 對於問題2的解決方案是延時加載。
1 public class SingletonClass { 2 private static SingletonClass instance = null; 3 4 private SingletonClass(){} 5 6 public static SingletonClass getInstance(){ 7 if(null == instance){ 8 instance = new SingletonClass(); 9 } 10 11View Code 不在類加載時創建,等到需要使用的時候在進行創建。 三、線程安全 方案2 解決了問題3,但這樣做又會引出新的問題4:同步。假如A、B 兩個線程同時調用getInstance(),A 進行null == instance判斷通過,然後instance對象進行創建且沒有創建完或者 A 讓出CPU時間片。在此時 B 執行到nulll == instance這裏,也能通過判斷。結果就是 A、B 拿到的是不同的對象。針對這種情況,加鎖就好。return instance; 12 } 13 14 }
方案3:
1 public class SingletonClass { 2 private static SingletonClass instance = null; 3 4 private SingletonClass(){} 5 6 public static SingletonClass getInstance(){ 7 synchronized (SingletonClass.class) { 8 if(null == instance){ 9 instance = new SingletonClass(); 10 } 11 } 12 13 return instance; 14 } 15 16 }View Code 在每次 null==instance判斷時加鎖,保證同步。 四、性能優化 方案3解決了同步問題,但是每次調用getInstance()都加一次鎖,性能有點低哦。那能不能在同步代碼塊外面再加一個非空判斷呢? 方案4:
1 public class SingletonClass { 2 private static SingletonClass instance = null; 3 4 private SingletonClass(){} 5 6 public static SingletonClass getInstance(){ 7 if(null == instance){ 8 synchronized (SingletonClass.class) { 9 System.out.println("進入鎖"); 10 if(null == instance){ 11 instance = new SingletonClass(); 12 } 13 } 14 } 15 return instance; 16 } 17 18 public static void main(String[] args) { 19 for (int i = 0; i < 10000; i++) { 20 SingletonClass.getInstance(); 21 } 22 System.out.println("處理完成"); 23 } 24 25 }View Code 運行得到的結果: 編譯後的代碼: 編譯後的代碼與代碼邏輯一致,做兩層非空判斷,線程只在第一次進入了鎖,後面都是直接返回instance對象。 方案4可行,但是總感覺怪怪的,還有沒有其他寫法呢? 答案是有,使用內部類,在調用時加載它。 方案5:
1 public class SingletonClass { 2 3 private SingletonClass(){} 4 5 private static class innerSingletonClass{ 6 private static final SingletonClass instance = new SingletonClass(); 7 } 8 9 public static SingletonClass getInstance(){ 10 return innerSingletonClass.instance; 11 } 12 13 }View Code
五、反序列化 上面方案4和方案5 已經能應付絕大多數的單例需求了,但是它們都是在沒有考慮問題1、問題2 這兩種比較極端的情況。先來看反序列化(序列化的相關知識自行搜索)。 方案6:
1 public class SingletonClass implements Serializable{ 2 private static final long serialVersionUID = 1L; 3 4 private static SingletonClass instance = null; 5 6 private SingletonClass(){} 7 8 public static SingletonClass getInstance(){ 9 if(null == instance){ 10 synchronized (SingletonClass.class) { 11 if(null == instance){ 12 instance = new SingletonClass(); 13 } 14 } 15 } 16 return instance; 17 } 18 19 private Object readResolve(){//阻止生成新的實例,總是返回當前對象 20 return instance; 21 } 22 23 }View Code readResolve():如果類中定義了這個特殊序列化方法,在這個對象被序列化之後就會調用它。它必須返回一個對象,而該對象之後會成為 readObject 的返回值。 六、反射 通過反射調用構造方法來創建對象,要在代碼層面防範這種行為,現階段我還不知道,但是可以通過枚舉來防止反射破壞單例。 方案7:
1 public enum SeasonEnum { 2 /** 3 * 在加載枚舉類時初始化。 4 * 線程安全,與java的類加載機制相關。 5 * 全局唯一,反射不能更改。 6 * 反序列化後還是同一個對象 7 * */ 8 Spring(1); 9 10 private final int value;//可以像普通類一樣定義Field,但是要定義在 枚舉 後面,不然編譯器會報錯。 11 12 private SeasonEnum(int v) {//編譯器只允許器定義成私有的。 13 value = v; 14 } 15 // private SeasonEnum() {//報錯,不能重寫無參構造方法 16 // 17 // } 18 19 public int getValue() { 20 return value; 21 } 22 23 public static void main(String[] args) throws Exception { 24 Class<SeasonEnum> cla = SeasonEnum.class; 25 Constructor<?>[] colls = cla.getDeclaredConstructors();//獲得所有的構造器,拿到的是無參的創建枚舉的構造方法。 26 System.out.println("獲得的構造方法數量:" + colls.length);//獲得的構造方法數量:1 27 for (Constructor<?> constructor : colls) { 28 System.out.println("構造方法名稱:" + constructor.getName());//構造方法名稱:testEnum.SeasonEnum 29 /** 30 * 運行報錯 31 * Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects 32 * at java.lang.reflect.Constructor.newInstance(Constructor.java:402) 33 * */ 34 constructor.newInstance(0); 35 System.out.println(SeasonEnum.Spring); 36 } 37 38 } 39 }View Code 枚舉算是 現階段單例的終極解決方案,簡單線程安全,防反射,防反序列化。 推薦在代碼中使用枚舉來實現單例! 參考: 《Effective Java》【Joshua Bloch 】 《Java 程序性能優化》【葛一鳴等】
單例模式