天天看點

Android 常用設計模式之——單例模式前言設計模式之單例模式

我們的态度是:每天進步一點點,理想終會被實作。

前言

繼上一篇Android常用設計模式之工廠模式,今天給大家講解一篇Android常用的設計模式——單例模式。我想單例模式應該是最常用的模式之一,可能很多老鐵認為單例模式已經是熟悉的不行了,但是我還是要寫一篇,作為記錄。

設計模式之單例模式

單例模式

說起單例模式,我想大家可能都清楚,通常我們的APP的一個類,在運作的時候可能有很多個對象,但是我們的單例模式不一樣,在我們的類中隻存在一個執行個體對象。

那麼在我們Android中經常在哪些場景會使用到我們的單例模式呢?

資料庫連接配接、線程池、配置檔案解析加載等一些非常耗時,占用系統資源的操作,并且還存在頻繁建立和銷毀對象,如果每次都建立一個執行個體,這個系統開銷是非常恐怖的,是以,我們可以始終使用一個公共的執行個體,以節約系統開銷。

  • 靜态成員變量
  • 私有構造方法
  • 全局通路

單例模式的分類

  • 餓漢模式
public class Singleton {

   private static Singleton instance = new Singleton();

   private Singleton(){}

   public static Singleton getInstance(){
       return instance;
   }
}
           

這種模式是隻要類一加載,那麼我們就建立了對象

我們測試一下:

public class SingletonTest {

   @Test
   public void getInstance(){
       Singleton s1 = Singleton.getInstance();
       Singleton s2 = Singleton.getInstance();

       System.out.println("對象1:" + s1.hashCode());
       System.out.println("對象2:" + s2.hashCode());
       if (s1 ==  s2) {
           System.out.println("對象相等");
       } else {
           System.out.println("對象不等");
       }
   }
}
           

此時,我們調用兩次 Singleton 類的 getInstance() 方法來擷取 Singleton 的執行個體。我們發現 s1 和 s2 是同一個對象。

  • 懶漢模式

懶漢模式,是一種延遲加載。在我們建立類的時候是不會立馬建立對象,隻有在我們需要的時候才會加載。

public class Singleton2 {

   private Singleton2(){}

   private static Singleton2 instance = null;

   public static Singleton2 getInstance(){
       if(instance == null){
           instance = new Singleton2();
       }
       return instance;
   }
}
           

這種模式有個缺點就是線程并不安全,當兩個線程同時調用的時候,線程1調用了getInstance()的時候,instance并沒有建立完成,這時線程2又在調用getInstance(),又會從新建立一次對象,此時就不能保證我們的對象單一性了,是以線程并不安全。

  • 懶漢模式線程安全版
public class Singleton3 {

   private Singleton3(){}

   private static Singleton3 instance = null;

   public static synchronized Singleton3 getInstance(){
       if(instance == null){
           instance = new Singleton3();
       }
       return instance;
   }
}
           

上面的案例,在多線程中工作且線程安全,但是每次調用 getInstance() 方法都需要進行線程鎖定判斷,在多線程高并發通路環境中,将會導緻系統性能下降。事實上,不僅效率很低,99%情況下不需要線程鎖定判斷。

  • 懶漢模式線程安全進階版
public class Singleton4 {

   private Singleton4(){}

   private static Singleton4 instance = null;

   public static Singleton4 getInstance(){
       if(instance == null){
           synchronized(Singleton4.class){
               if(instance == null){
                   instance = new Singleton4();
               }
           }    
       }
       return instance;
   }
}
           

這種性能方面就優于前面那種,這種模式等于兩次校驗,第一次是判斷是否建立對象,如果沒有再以同步的方式建立對象。

public class Singleton5 {
   private Singleton5() {}

   private static class SigletonHolder {
       private final static Singleton5 instance = new Singleton5();
   }

   public static Singleton5 getInstance() {
       return SigletonHolder.instance;
   }
}
           
  • 枚舉
public enum SingletonEnum {
   INSTANCE;
   private SingletonEnum(){}
}
           

枚舉的特點是,構造方法是 private 修飾的,并且成員對象執行個體都是預定義的,是以我們通過枚舉來實作單例模式非常的便捷。這種模式很少見,而且我們也不常用,本身枚舉在性能方面差,占用記憶體較多,隻需要了解即可。

  • 靜态内部内
public class Singleton5 {
   private Singleton5() {}

   private static class Sigleton {
       private final static Singleton5 instance = new Singleton5();
   }

   public static Singleton5 getInstance() {
       return SigletonHolder.instance;
   }
}
           

類加載的時候并不會執行個體化 Singleton5,而是在第一次調用 getInstance() 加載内部類 Sigleton,此時才進行初始化 instance 成員變量,確定記憶體中的對象唯一性。

單例模式 vs 靜态方法

如果認為單例模式是非靜态方法。而靜态方法和非靜态方法,最大的差別在于是否常駐記憶體,實際上是不對的。它們都是在第一次加載後就常駐記憶體,是以方法本身在記憶體裡,沒有什麼差別,是以也就不存在靜态方法常駐記憶體,非靜态方法隻有使用的時候才配置設定記憶體的結論。

是以,我們要從場景的層面來剖析這個問題。如果一個方法和他所在類的執行個體對象無關,僅僅提供全局通路的方法,這種情況考慮使用靜态類,例如 java.lang.Math。而使用單例模式更加符合面向對象思想,可以通過繼承和多态擴充基類。此外,上面的案子中,單例模式還可以進行延伸,對執行個體的建立有更自由的控制。

volatile 修飾

對象的建立并不是一個原子操作,在 new 對象的時候其實是有 3 步:配置設定記憶體,初始化和指派。由于 java 是允許處理器進行亂序執行的,是以有可能是先指派再初始化,這樣懶漢模式就有異常了,解決方法是給這個靜态對象加 volatile 字段來防止亂序執行。

這裡對volatile不做詳細的解釋,感興趣的可以檢視(https://www.cnblogs.com/dolphin0520/p/3920373.html)

總結

我們始終記得一個原則就是:單例模式始終保證一個類隻有一個執行個體對象存在,及唯一性。

如果采用餓漢式,在類被加載時就執行個體化,是以無須考慮多線程安全問題,并且對象一開始就得以建立,性能方面要優于懶漢式。

如果采用懶漢式,采用延遲加載,在第一次調用 getInstance() 方法時才執行個體化。好處在于無須一直占用系統資源,在需要的時候再進行加載執行個體。但是,要特别注意多線程安全問題,我們需要考慮使用雙重校驗鎖的方案進行優化。

實際上,我們應該采用餓漢式還是采用懶漢式,取決于我們希望空間換取時間,還是時間換取空間的抉擇問題,是以選擇哪種模式,隻是取決于我們實際項目中适合哪種。

最後想說一點,靜态内部類也是非常不錯的實作方式。

溫馨提示:

我建立了一個技術交流群,群裡有各個行業的大佬都有,大家可以在群裡暢聊技術方面内容,以及文章推薦;如果有想加入的夥伴加我微信号【luotaosc】備注一下“加群” 另外關注公衆号,還有一些個人收藏的視訊:

回複“學習資源” ,擷取學習視訊。

原創文章不易,如果覺得寫得好,掃碼關注一下點個贊,是我最大的動力。

關注我,一定會有意想不到的東西等你: 每天專注分享Android幹貨

Android 常用設計模式之——單例模式前言設計模式之單例模式

備注:程式圈LT