1. 程式人生 > >JAVA中常用的幾種設計模式--單例

JAVA中常用的幾種設計模式--單例

前段時間面試的時候被問到了設計模式,結果想想只瞭解單例、工廠…囧,所以整理下,溫故而知新。
設計模式:簡單說就是前人留下的一些經驗,有助於提高程式碼的複用率,增加可讀性;
單例模式應該是使用比較多的模式之一,很多人都是一知半解,其中也包括我,哈哈

單例模式

定義:確保一個類只有一個例項,並且提供一個全域性訪問點;
優點:該類的例項只有一個;全域性共享;縮短物件的建立時間;

七種寫法

單例模式的各種寫法其實有利有弊,簡單分析下

餓漢模式

public class Singleton {  
     private static Singleton instance = new
Singleton(); private Singleton (){ } public static Singleton getInstance() { return instance; } }

在類載入的時候就建立,以空間換時間的做法。

java中的runtime 類就是採用的餓漢模式

public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class <code>Runtime</code> are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return
the <code>Runtime</code> object associated with the current * Java application. */
public static Runtime getRuntime() { return currentRuntime; } /** Don't let anyone else instantiate this class */ private Runtime() {} //以下程式碼省略 }

總結:這種方式簡單粗暴,對於佔用記憶體少,載入時間短的部分單例可以使用這種方式。但是對於啟動時間有要求,佔用記憶體資源較多的類 不建議這麼寫。

懶漢模式

  • 非執行緒安全

    public class Singleton {  
      private static Singleton instance;  
      private Singleton (){
      }   
      public static Singleton getInstance() {  
          if (instance == null) {  
              instance = new Singleton();  
          }  
          return instance;  
      }  
    }

    懶漢模式申明瞭一個靜態物件,在第一次載入的時候會例項化。延時載入了,但是是非執行緒安全的,可能建立多個物件例項。

    • 執行緒安全
public class Singleton {  
      private static Singleton instance;  
      private Singleton (){
      }
      public static synchronized Singleton getInstance() {  
          if (instance == null) {  
              instance = new Singleton();  
          }  
          return instance;  
      }  
 }

因為這種方式在getInstance()方法上加了同步鎖,所以在多執行緒情況下會造成執行緒阻塞,把大量的執行緒鎖在外面,只有一個執行緒執行完畢才會執行下一個執行緒。因為鎖住getInstance 導致執行的效率比較的低;

  • 執行緒安全,雙重校驗鎖

public class Singleton {

    /**
     * 注意此處使用的關鍵字 volatile,
     * 被volatile修飾的變數的值,將不會被本地執行緒快取,
     * 所有對該變數的讀寫都是直接操作共享記憶體,從而確保多個執行緒能正確的處理該變數。
     */
    private volatile static Singleton singleton;
    private Singleton() {
    }
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized(Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return singleton;
    }
}

這種寫法在getSingleton()方法中對singleton進行了兩次判空,第一次是為了不必要的同步,第二次是在singleton等於null的情況下才建立例項。
雙重校驗鎖:既可以達到執行緒安全,也可以使效能不受很大的影響,換句話說在保證執行緒安全的前提下,既節省空間也節省了時間,集合了「餓漢式」和兩種「懶漢式」的優點,取其精華,去其槽粕。

需要說明的是: 對於volatile關鍵字,還是存在很多爭議的。由於volatile關鍵字可能會遮蔽掉虛擬機器中一些必要的程式碼優化,所以執行效率並不是很高。也就是說,雖然可以使用“雙重檢查加鎖”機制來實現執行緒安全的單例,但並不建議大量採用,可以根據情況來選用。
還有就是在java1.4及以前版本中,很多JVM對於volatile關鍵字的實現的問題,會導致“雙重檢查加鎖”的失敗,因此“雙重檢查加鎖”機制只只能用在java1.5及以上的版本。

  • 靜態內部類

很多時候,java都給我們提供了同步的控制;
比如:
static{…}區塊初始化資料的時候。
final變數被訪問的時候。

因為在JVM進行類載入的時候他會保證資料是同步的,我們可以這樣實現:採用內部類,在這個內部類裡面去建立物件例項。這樣的話,只要應用中不使用內部類 JVM 就不會去載入這個單例類,也就不會建立單例物件,從而實現「懶漢式」的延遲載入和執行緒安全。

public class Singleton { 
    private Singleton(){
    }
      public static Singleton getInstance(){  
        return SingletonHolder.sInstance;  
    }  
    private static class SingletonHolder {  
        private static final Singleton sInstance = new Singleton();  
    }  
}
  • 列舉

《Java與模式》中,作者這樣寫道,使用列舉來實現單例項控制會更加簡潔,而且無償地提供了序列化機制,並由JVM從根本上提供保障,絕對防止多次例項化,是更簡潔、高效、安全的實現單例的方式。

public enum Singleton {
     //定義一個列舉的元素,它就是 Singleton 的一個例項
     INSTANCE;  

     public void doSomeThing() {  
         // do something...
     }  
 }

使用方式:

public static void main(String args[]) {
    Singleton singleton = Singleton.instance;
    singleton.doSomeThing();
}

實現簡單,但是可讀性比較的差

  • 使用容器
public class SingletonManager { 
  private static Map<String, Object> objMap = new HashMap<String,Object>();
  private Singleton() { 
  }
  public static void registerService(String key, Objectinstance) {
    if (!objMap.containsKey(key) ) {
      objMap.put(key, instance) ;
    }
  }
  public static ObjectgetService(String key) {
    return objMap.get(key) ;
  }
}

這種事用SingletonManager 將多種單例類統一管理,在使用時根據key獲取物件對應型別的物件。這種方式使得我們可以管理多種型別的單例,並且在使用時可以通過統一的介面進行獲取操作,降低了使用者的使用成本,也對使用者隱藏了具體實現,降低了耦合度。

總結:
對於以上七種單例,分別是「餓漢式」、「懶漢式(非執行緒安全)」、「懶漢式(執行緒安全)」、「雙重校驗鎖」、「靜態內部類」、「列舉」和「容器類管理」。很多時候取決人個人的喜好,雖然雙重檢查有一定的弊端和問題,但我就是鍾愛雙重檢查,覺得這種方式可讀性高、安全、優雅(個人觀點)。