如何寫出一個性能優化的單例模式

  • 2019 年 10 月 5 日
  • 筆記

單例模型是面試當中最常見的一種設計模式,它是一種對象創建模式,用於產生一個對象的具體實例,可以確保系統中一個類只產生一個實例。

簡而言之,單例模式可以帶來兩個好處:

1、對於頻繁使用到的對象,可以節省反覆創建對象花費的時間;

2、減少對象的實例化操作,故而可以降低系統記憶體的使用頻率;

根據以上兩點,可看出使用單例模式能夠有效地改善系統的性能。

最常見的單例模式有餓漢模式與懶漢模式。

1、餓漢模式長這樣的:

 1 public class Singleton{   2     3   private Singleton(){}   4      5   private static final Singleton instance=new Singleton();   6   7   public static Singleton getInstance(){   8      9   return instance;  10   }  11 }

這種單例模式非常簡單,唯一不足的是,無法對instance實例做延遲載入,由於instance成員變數是static定義的,因此JVM在載入單例類時,單例對象就會被建立,如果這個單例類在系統中還包含了其他的靜態方法,每次通過這個單例類去調用其他的靜態方法時,就會載入被static定義的成員變數,也就是載入了private static final Singleton instance=new Singleton(),故而就會創建一個Singleton實例出來,可以通過一個例子來進行說明:

 1 public class Singleton{   2   3     private Singleton(){   4   5         System.out.println("創建了一個單例!");   6     }   7   8     private static final Singleton instance=new Singleton();   9  10     public static Singleton getInstance(){  11  12         return instance;  13     }  14  15     public static void Test(){  16  17         System.out.println("調用了這個方法!");  18     }  19  20 }

列印出這樣的資訊:

由此可見,餓漢模式因為沒有延遲載入機制,存在著對象容易被創建的問題,這將會影響系統在調用相關函數時的反應速度,可以加入延遲載入機制來解決這個問題。

加了延遲機制的單例模式,就成了我們常見的懶漢模式了,但這裡加了同步安全機制:

 1 public class SingletonSyn{   2   3 private SingletonSyn(){   4     System.out.println("創建了一個執行緒安全的懶漢單例!");   5 }   6   7     private static  SingletonSyn instance=null;   8   9     public static synchronized SingletonSyn getInstance() {  10     if (instance == null)  11         instance = new SingletonSyn();  12         return instance;  13  14     }  15 }

這裡需注意的地方是:getInstance()方法必須是同步的,否則在多執行緒環境下,當執行緒1正新建單例時,完成操作賦值時,執行緒2可能判斷instance為null,故執行緒2也將啟動新建單常式序,這樣就會導致多個實例被創建,對性能的影響將會加劇,故加synchronized做同步是必須的。

可謂成也蕭何敗也蕭何,雖然加上了同步關鍵字synchronized 可以解決同步問題,但因在多執行緒的環境下,它的性能消耗將遠遠大於第一種餓漢模式。

基於前面兩種單例模式,可以對它做進一步的改進:

 1 public class Singleton{   2     3   private Singleton(){System.out.println("創建了一個基於內部類的單例模式!");}   4      5   private static class SingletonHolder{   6      private static Singelton instance=new Singleton();   7   8 }   9  10   public static  Singleton getInstance(){  11    12   return SingletonHolder.instance;  13    14    }  15 }

上面的例子使用內部類來維護單例的實例,當Singleton被載入時,其內部類並不會被初始化,故可以確保當Singleton類被載入JVM時,不會初始化單例類,只有當getInstance()被調用時,才會載入內部類SingletonHolder,從而實例化單例類instance。同時,由於實例的建立是在類載入時才完成的,故天生對多執行緒友好,getInstance方法也無需使用同步synchronized,可見,使用內部類方式實現單例,既可以做到延遲載入,也不必使用同步關鍵字,是一種比較完善的實現。

當然,若需更加完善單例模式的設計,還有更優的方式,感興趣的夥伴可以繼續深入進行一個探討。

我的部落格即將同步至騰訊雲+社區,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=2lfj12z61ny88