設(shè)計模式1:單例模式

在平時工作中,很多時候遇見的任務(wù)其實都有多種實現(xiàn)方法,或者說有不同的代碼設(shè)計。
當然,就安卓開發(fā)而言,最重要的就是剛開始的架構(gòu)。不過后來實現(xiàn)具體業(yè)務(wù)的代碼同樣可以追求更“優(yōu)雅”一些。
何謂“優(yōu)雅”?編程里面的優(yōu)雅,無非就是在確保正確性、穩(wěn)定性的前提下,做到:

  • 邏輯清晰,易于理解
  • 耦合度低,容易復(fù)用
  • 高效

實際上,說起來就這么一些,真正做起來可不容易。光正確性、穩(wěn)定性就要花很大功夫,在這之外考慮一些東西,就是難上加難。
好在大部分問題別人都遇到并且解決過,而長久以來積累的一些編程的經(jīng)驗,就成為了設(shè)計模式(Design Patterns)。
所以說,為什么要用設(shè)計模式?因為用了設(shè)計模式之后代碼會比較“優(yōu)雅”。
當然,羅老師說的好,沒有設(shè)計就是最好的設(shè)計。設(shè)計模式只是一種參考,不能生搬硬套。否則還真不如沒有。
今天就來學(xué)一學(xué)最簡單的——單例模式(Singleton)。

單例模式

基本概念

這個模式的意圖是希望對象只創(chuàng)建一個實例,并且提供一個全局的訪問點。
為什么要用單例模式?單例模式的對面自然是多例,為什么不能有多個實例呢?
答案很明顯,一是節(jié)約資源,比如SimpleDateFormat,到處創(chuàng)建的話,效率肯定不如單個的好(當然這個類線程不安全,復(fù)用的時候需要注意);二是方便管理,比如DB的Manager類,1個DB只需要1個Manager,多了的話反而會變得混亂。
同樣的,單例模式的局限性也很明顯:

  • 全局可見,可能要考慮線程安全性;
  • 全局調(diào)用,因此難以釋放;
  • 繼承基本上沒用;
  • 耦合度高。

創(chuàng)建方法

就大體而言,我把單例模式的創(chuàng)建方法分成兩種:

1.預(yù)先初始化(Eager Initialization)

預(yù)先初始化的意思就是,只要有機會就創(chuàng)建實例。這個方法是線程安全的(因為沒有帶入更多邏輯,這里說的線程安全都是對創(chuàng)建單例而言)。

2. 延遲初始化(Lazy Initialization)

和1相反,是在最后時刻也就是馬上要用的時候來創(chuàng)建實例。這個不是天生線程安全的,需要做一些處理。處理又有不同的處理方法,具體再說。

代碼

廢話不多說,上代碼了(出處參考這里):

1. 預(yù)先初始化
/**
 * Singleton class. Eagerly initialized static instance guarantees thread safety.
 */
public final class IvoryTower {

  /**
   * Private constructor so nobody can instantiate the class.
   */
  private IvoryTower() {}

  /**
   * Static to class instance of the class.
   */
  private static final IvoryTower INSTANCE = new IvoryTower();

  /**
   * To be called by user to obtain instance of the class.
   *
   * @return instance of the singleton.
   */
  public static IvoryTower getInstance() {
    return INSTANCE;
  }
}

這個類就是最簡單的預(yù)先加載單例模式。私有構(gòu)造方法從而不會被外界調(diào)用,私有靜態(tài)實例創(chuàng)建,因此叫做“預(yù)加載”。同時final修飾,不可變。
這個方法是線程安全的,因為實例在被調(diào)用之前已經(jīng)生成,而且不可變。缺點自然是有浪費資源的嫌疑,因為沒被調(diào)用時已經(jīng)占用了內(nèi)存。
適用場景:

  • 需要經(jīng)常使用;
  • 不會占用太多資源。
2.基于枚舉的預(yù)先初始化
/**
 * Enum based singleton implementation. Effective Java 2nd Edition (Joshua Bloch) p. 18
 */
public enum EnumIvoryTower {

  INSTANCE;

  @Override
  public String toString() {
    return getDeclaringClass().getCanonicalName() + "@" + hashCode();
  }
}

使用時調(diào)用EnumIvoryTower.INSTANCE。
看上去更加簡單,也線程安全,原理和1類似。不過需要注意的是擴展會受到限制:繼承、序列化等等。

3. 線程安全的延遲初始化
/**
 * Thread-safe Singleton class. The instance is lazily initialized and thus needs synchronization
 * mechanism.
 *
 * Note: if created by reflection then a singleton will not be created but multiple options in the
 * same classloader
 */
public final class ThreadSafeLazyLoadedIvoryTower {

  private static ThreadSafeLazyLoadedIvoryTower instance;

  private ThreadSafeLazyLoadedIvoryTower() {}

  /**
   * The instance gets created only when it is called for first time. Lazy-loading
   */
  public static synchronized ThreadSafeLazyLoadedIvoryTower getInstance() {

    if (instance == null) {
      instance = new ThreadSafeLazyLoadedIvoryTower();
    }

    return instance;
  }
}

這個方法就是在被調(diào)用實例的時候去創(chuàng)建一個,而為了線程安全,使用synchronized 來修飾getInstance方法。好處就是資源分配更合理,要用了才占資源;壞處就是速度慢了,創(chuàng)建可能會慢,而且多個線程一起訪問的時候還有鎖。

4. 線程安全的雙重檢驗鎖延遲初始化
/**
 * Double check locking
 * <p/>
 * http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
 * <p/>
 * Broken under Java 1.4.
 *
 * @author mortezaadi@gmail.com
 */
public final class ThreadSafeDoubleCheckLocking {

  private static volatile ThreadSafeDoubleCheckLocking instance;

  /**
   * private constructor to prevent client from instantiating.
   */
  private ThreadSafeDoubleCheckLocking() {
    // to prevent instantiating by Reflection call
    if (instance != null) {
      throw new IllegalStateException("Already initialized.");
    }
  }

  /**
   * Public accessor.
   *
   * @return an instance of the class.
   */
  public static ThreadSafeDoubleCheckLocking getInstance() {
    // local variable increases performance by 25 percent
    // Joshua Bloch "Effective Java, Second Edition", p. 283-284
    
    ThreadSafeDoubleCheckLocking result = instance;
    // Check if singleton instance is initialized. If it is initialized then we can return the instance.
    if (result == null) {
      // It is not initialized but we cannot be sure because some other thread might have initialized it
      // in the meanwhile. So to make sure we need to lock on an object to get mutual exclusion.
      synchronized (ThreadSafeDoubleCheckLocking.class) {
        // Again assign the instance to local variable to check if it was initialized by some other thread
        // while current thread was blocked to enter the locked zone. If it was initialized then we can 
        // return the previously created instance just like the previous null check.
        result = instance;
        if (result == null) {
          // The instance is still not initialized so we can safely (no other thread can enter this zone)
          // create an instance and make it our singleton instance.
          instance = result = new ThreadSafeDoubleCheckLocking();
        }
      }
    }
    return result;
  }
}

這個是3的加強版,提高了效率,但是代價就是代碼的復(fù)雜度。Java 1.4以下不可用。
其實很精妙,instance用volatile修飾,雖然是靜態(tài)變量但剛開始并沒有初始化。Volatile的意思就是不會被重排序,讀取的總是最后寫入的值。當然光靠Volatile并不能解決線程安全問題,因為可能兩個線程同時讀之后再寫。因此后面還有一個synchronized (ThreadSafeDoubleCheckLocking.class),直接鎖類。
假設(shè)兩個線程AB先后嘗試獲取instance,假如線程不安全,后果就是創(chuàng)建兩遍instance。在這里,A肯定不會創(chuàng)兩遍,主要看B,B進來的時候假如A已經(jīng)完事了自然好說,假設(shè)A還在創(chuàng)建當中,B一看是null,準備去創(chuàng)建,發(fā)現(xiàn)門被鎖上了(synchronized),等到門開了的時候,A已經(jīng)出來了,也在里面創(chuàng)建了實例,B再看的時候就不是null了。
4比3快,因為只同步塊而不是方法。

5. InitializingOnDemandHolderIdiom(實在不知道怎么翻譯)
/**
 * The Initialize-on-demand-holder idiom is a secure way of creating a lazy initialized singleton
 * object in Java.
 * <p>
 * The technique is as lazy as possible and works in all known versions of Java. It takes advantage
 * of language guarantees about class initialization, and will therefore work correctly in all
 * Java-compliant compilers and virtual machines.
 * <p>
 * The inner class is referenced no earlier (and therefore loaded no earlier by the class loader) than
 * the moment that getInstance() is called. Thus, this solution is thread-safe without requiring special
 * language constructs (i.e. volatile or synchronized).
 *
 */
public final class InitializingOnDemandHolderIdiom {

  /**
   * Private constructor.
   */
  private InitializingOnDemandHolderIdiom() {}

  /**
   * @return Singleton instance
   */
  public static InitializingOnDemandHolderIdiom getInstance() {
    return HelperHolder.INSTANCE;
  }

  /**
   * Provides the lazy-loaded Singleton instance.
   */
  private static class HelperHolder {
    private static final InitializingOnDemandHolderIdiom INSTANCE =
        new InitializingOnDemandHolderIdiom();
  }
}

這個方法使用了靜態(tài)內(nèi)部類來獲得實例,原理就是內(nèi)部類在被調(diào)用之前不會創(chuàng)建實例。代碼量少,不需要手動同步。

總結(jié)

不要看單例模式看起來簡單,大神們的實現(xiàn)都是直指Java核心,編譯原理,線程安全,都考慮到了。這5種方法就是大浪淘沙,無數(shù)程序員智慧的結(jié)晶。具體使用哪種方法得看需求。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容