1. 程式人生 > >單例模式

單例模式

實現 程序 先來 null effective 如果 ava 不同的 aps

單例模式:確保某一個類只有一個實例,而且自行實例化並向整個系統提供這個實例。 優點: 1、省略創建對象所花費的時間減少系統開銷,尤其是重量級對象。 2、減少對象的創建,減輕GC壓力。 3、設置全局訪問入口,優化資源訪問。 一、最簡單的實現 方案1: 技術分享
 1 public class SingletonClass {
 2     private static final SingletonClass instance = new SingletonClass();
 3     
 4     private SingletonClass(){}
 5     
 6     public
static SingletonClass getInstance(){ 7 return instance; 8 } 9 10 }
View Code SingletonClass對象聲明為final類型結合Java的類加載機制,保證了對象的唯一性。 二、延時加載 方案1乍看上去簡單可行,但會出現兩個問題: 問題1:可以通過反射再創建實例。 問題2:可以通過反序列化創建實例。
以上兩個問題有點偏,先掩耳盜鈴不考慮。 問題3:對象在類加載時就創建了不管使用與否,創建對象消耗小還好,消耗大的話就容易造成浪費。 對於問題2的解決方案是延時加載。
方案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         
11
return instance; 12 } 13 14 }
View Code 不在類加載時創建,等到需要使用的時候在進行創建。 三、線程安全 方案2 解決了問題3,但這樣做又會引出新的問題4:同步。假如A、B 兩個線程同時調用getInstance(),A 進行null == instance判斷通過,然後instance對象進行創建且沒有創建完或者 A 讓出CPU時間片。在此時 B 執行到nulll == instance這裏,也能通過判斷。結果就是 A、B 拿到的是不同的對象。針對這種情況,加鎖就好。
方案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 程序性能優化》【葛一鳴等】



單例模式