設計模式--單例模式
單例模式是一種最常見的設計模式。
為什麼
所謂單例模式,就是一個類僅有一個例項存在。為什麼有這種要求呢,因為一個類有多個物件的話,可能會消耗過多的資源,以Android中常用的圖片載入框架ImageLoader為例,ImageLoader中包含執行緒池,快取系統,網路請求等,很消耗資源,因此沒有理由讓它構造多個例項,我們在使用的時候,也是通過ImageLoader.getInstance()獲得ImageLoader的例項。
定義
保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。

要實現單例模式,主要有以下幾個關鍵點:
- 構造方法不對外開放,為private
Java中建立物件主要是通過new來建立,但是單例模式不允許外接通過new的方法來建立物件。 - 通過一個靜態方法返回單例物件
- 要保證物件只有一個,尤其是在多執行緒環境下
所有單例模式的UML圖如下所示:

UML圖比較簡單,只有一個例項,提供一個獲取例項的方法。
單例模式的寫法比較多,關鍵要知道每種寫法的優劣,選擇適合自己的寫法即可:
寫法一,餓漢模式(靜態常量,可用)
public class Singleton(){ private static Singleton instance=new Singleton(); private Singleton(){ } public static Singleton getInstance(){ return instance; } ... }
優缺點
可以看到這個例項,
優點:在類裝載的時候就完成初始化,避免了執行緒同步問題。
缺點:在類載入的時候建立,即使用不到,也會建立,在一定程度上造成了資源的浪費。
寫法二,懶漢模式(執行緒不安全)
public class Singleton { private static Singleton instance=null;//利用一個靜態變數來記錄Singleton的唯一例項 private Singleton() {//構造方法宣告為私有,拒絕外接通過new的方式建立例項 } public static Singleton getInstance() {//提供一個static方法,提供唯一的例項 if (instance == null) { instance = new Singleton(); } return instance; } }
缺點:懶漢形式存在的問題是,在多執行緒下不能保證例項只有一個。懶漢模式有個判空操作,
if (instance == null) { instance = new Singleton(); }
初始時instance是空的,如果有多個執行緒同時執行至此,有可能會建立多個例項,instance就不唯一了。所以對於高併發來建立instance的操作,懶漢模式並不實用。
理解懶漢和餓漢
懶漢式,餓漢式這是一種比較形象的稱呼。
餓漢式,既然餓,在建立物件的時候比較著急,在裝載類的時候就建立物件。
懶漢式,既然懶,在建立例項物件的時候就不著急,會一直等到馬上要使用物件例項的時候建立。
補充
懶漢模式和餓漢模式,在建立例項的時候,都有個static關鍵字。
懶漢模式:
private static Singleton instance=null;
餓漢模式:
private static Singleton instance=new Singleton();
那麼這兩個static到底哪個用到了static的特性呢。Java中static的特性描述如下:
- static變數在類載入的時候初始化。
- 多個變數的static變數會公共享同一塊記憶體區域
這樣看來,只有餓漢模式用到了static的特性,那麼懶漢模式呢,因為懶漢模式中instance用於static修飾的方法中,所以必須要加static。
寫法三,懶漢模式(執行緒安全)(不推薦用)
private static Singleton instance = null; public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; }
可以看到,每次在執行getInstance()方法時,都要進行同步一下。
優點:解決了懶漢模式執行緒不安全的問題。
缺點:效率變低,每次getInstance()都要同步一下,其實只要初始的時候同步一下就可以,後面要想獲取例項的話,只要return就可以了,每次都同步會造成效率變低。
寫法四,雙重檢查(推薦用)
private static volatile Singleton instance; public static Singleton getInstance(){ if (instance==null){ synchronized (Singleton.class){ if (instance==null){ instance=new Singleton(); } } } return instance; }
可以看到,有兩次檢查instnace是否為空的判斷,第一次判斷是為了解決效率問題,只有當instance為空時,才會同步程式碼。第二次判空是為了防止出現多個例項。其中volatile關鍵字是為了防止指令重排。
寫法五,靜態內部類(推薦用)
private static class SingletonInstance{ private static final Singleton INSTANCE=new Singleton(); } public static Singleton getInstance(){ return SingletonInstance.INSTANCE; }
由於SingletonInstance是一個靜態內部類,它載入的時機是當它被呼叫的時候,它在外部類的getInstance()方法被呼叫,所以只有在getInstance()方法被呼叫時,才會去載入,達到了延遲載入的目的。
對於內部類SingletonInstance,它採用了類似於餓漢式的單例模式實現,保證了例項的單一。
寫法六,列舉
public enum SingleInstance { INSTANCE; public void doSomething(){ // do something } }
使用
SingleInstance.INSTANCE.fun1();
最簡單的單例模式,既保證了執行緒安全,也沒有同步問題,不過在時機專案中,很少見到在實際的專案中運用。