1. 程式人生 > >23種設計模式之---單例模式(Singleton Pattern)

23種設計模式之---單例模式(Singleton Pattern)

1.單例模式

單例模式(Singleton):保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點. 這種設計模式屬於建立型設計模式,提供了一種建立物件的最佳方式.

       通常我們可以讓一個全域性變數使得一個物件被訪問,但它不能防止你例項化多個物件.一個最好的辦法就是,讓類自身負責儲存它的唯一例項.這個類可以保證沒有其他例項可以被建立,並且它可以提供一個訪問該例項的方法.

2.單例類的特徵:

     1.單例類只能有一個例項;

     2.單例類必須自己建立自己的唯一例項;

     3.單例類必須給所有其他物件提供這一例項.

3.單例模式的分類: 餓漢式和懶漢式

餓漢式:餓漢式就是類一旦載入就把單例初始化完成,保證呼叫getInstance()方法的時候,單例是已經存在了的.

public class Singleton {
    /**
     *是否 Lazy 初始化:否
     *是否多執行緒安全:是
     *實現難度:易
     *描述:這種方式比較常用,但容易產生垃圾物件。
     *優點:沒有加鎖,執行效率會提高。
     *缺點:類載入時就初始化,浪費記憶體。
     *它基於 classloder 機制避免了多執行緒的同步問題,
     * 不過,instance 在類裝載時就例項化,雖然導致類裝載的原因有很多種,
     * 在單例模式中大多數都是呼叫 getInstance 方法,
     * 但是也不能確定有其他的方式(或者其他的靜態方法)導致類裝載,
     * 這時候初始化 instance 顯然沒有達到 lazy loading 的效果。
     */
    private static Singleton instance = new Singleton();
    private Singleton (){}
    public static Singleton getInstance() {
        System.out.println("instance:"+instance);
        System.out.println("載入餓漢式....");
        return instance;
    }
}

懶漢式: 懶漢式比較懶,類載入時並不會建立這一例項,在呼叫getInstance()方法時,才會建立這一例項.

public class Singleton {
    /**
     *是否 Lazy 初始化:是
     *是否多執行緒安全:否
     *實現難度:易
     *描述:這種方式是最基本的實現方式,這種實現最大的問題就是不支援多執行緒。因為沒有加鎖 synchronized,所以嚴格意義上它並不算單例模式。
     *這種方式 lazy loading 很明顯,不要求執行緒安全,在多執行緒不能正常工作。
     */
    private static Singleton instance;
    private Singleton (){}

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

4.餓漢式和懶漢式的區別:

       1.執行緒安全:

      餓漢式天生就是執行緒安全的,可以直接用於多執行緒而不會出現問題,

      懶漢式本身是非執行緒安全的,為了實現執行緒安全有幾種寫法(網上還有好多個執行緒安全的寫法,可以參考):

(1).同步方法:

public class Singleton {
	//私有化構造
	private Singleton(){
		System.out.println("物件建立成功");
	}
	//全域性物件
	private static Singleton singleton = null;	
	//呼叫getInstance方法才會建立物件
	public static synchronized Singleton getInstance(){
		//判斷全域性物件是否為空
		if(singleton == null){
			//如果為空,就建立該物件
			singleton = new Singleton();
		}
		//如果不為空,返回該物件
		return singleton;
	}
}

    雖然做到了執行緒安全,並且解決了多例項的問題,但是它並不高效。因為在任何時候只能有一個執行緒呼叫 getInstance()方法。但是同步操作只需要在第一次呼叫時才被需要,即第一次建立單例例項物件時。這就引出了雙重檢驗鎖。

(2).雙重檢驗鎖

public class Singleton {
    private volatile static Singleton instance; //宣告成 volatile 保證編譯器不進行優化
    private Singleton (){}
    public static Singleton getSingleton() {
        if (instance == null) {                         
            synchronized (Singleton.class) {
                if (instance == null) {       
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
   
}

關於volatile的作用,借鑑了一位大佬的評論:

(這裡在宣告變數時使用了volatile關鍵字來保證其執行緒間的可見性;在同步程式碼塊中使用二次檢查,以保證其不被重複例項化。集合其二者,這種實現方式既保證了其高效性,也保證了其執行緒安全性。" 二次檢查這段程式碼, 為什麼要二次檢查? volatile在此的作用僅是保證可見性? 我想糾正一下, 內層的檢查保證物件在併發時不會重複建立 和 ! 物件尚未初始化完成, 外層檢查避免每一次獲取物件都對資源進行加鎖, 影響效能, 所以才會有了併發情況下的執行緒安全的懶漢單例, 即Double Check Lock; 而volatile 在此是禁止指令重排的作用, 保證先初始化, 再把物件引用賦值給instance變數. )

(3).靜態內部類

public class Singleton {  
    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE; 
    }  
}

這種寫法仍然使用JVM本身機制保證了執行緒安全問題;由於 SingletonHolder是私有的,除了 getInstance()之外沒有辦法訪問它,因此它是懶漢式的;同時讀取例項的時候不會進行同步,沒有效能缺陷;也不依賴 JDK版本。

       2.資源載入和效能:

       餓漢式在類建立的同時就例項化一個靜態物件出來,不管之後會不會使用這個單例,都會佔據一定的記憶體,但是相應的,在第一次呼叫時速度也會更快,因為其資源已經初始化完成。

       而懶漢式顧名思義,會延遲載入,在第一次使用該單例的時候才會例項化物件出來,第一次呼叫時要做初始化,如果要做的工作比較多,效能上會有些延遲,之後就和餓漢式一樣了。

5.懶漢式、餓漢式在spring IOC中的應用

在spring IOC中,bean在xml中可以配置為singleton,而且有一個lazy-init屬性

lazy-init=true,設定延遲初始化, 在建立容器之後,在第一次從容器獲取物件的時候建立單例的物件

如果沒有配置或延遲初始化為預設值, 單例的物件會在建立容器的時候建立物件