1. 程式人生 > >java 設計模式 學習筆記(16) 單例模式

java 設計模式 學習筆記(16) 單例模式

單例模式:保證一個類僅有一個例項,並提供一個訪問其例項的一個全域性訪問點

根據單例模式的定義,寫一個單例模式的例子需要注意兩點:

1.例項有該類自己生成 

    為了防止客戶程式碼通過 new Singleton()來例項一個物件,需要將 Singleton的預設建構函式定義為private

2. 提供一個訪問其例項的全域性訪問點

    將訪問方法getInstance()定義為 static ,則直接通過Singleton.getInstance() 來獲取到例項

3.單例只能有一個例項

   在Singleton類中,將instance 定義為 static ,則保證了不管多少個 Singleton都只有並僅有一個instance 靜態變數。

單例的實現有兩種方式:

餓漢單例:在類被載入時就初始化例項

懶漢單例:類載入之後,第一次呼叫getInstance()方法時,才初始化例項。

              不過懶漢式單例的getInstance () 方法需要注意到執行緒的同步問題:在單例類已經被載入進來,但還沒有被呼叫生成例項之前,有多個執行緒同時呼叫getInstance() 方法,那麼則可能多次呼叫  instance = new Singelton_lazy()語句,導致出現多個例項的結果。

所以懶漢單例的getInstance() 方法要使用synchronized 來保證其執行緒安全性。

 

現在我們來模擬懶漢單例模式的getInstance() 如果不注意執行緒安全性時出現的執行緒不安全現象:

現在將Singleton_lazy 改變一下:  1. 去掉synchronized

                                                  2. 認為的改變getInstance() 方法,但傳入的引數為0時,則訪問SingletonFull的執行緒需要休眠500毫秒, 當500毫秒一過,則返回一個單例的例項

 現在編寫兩個執行緒類:

編寫測試用的程式碼:

 

輸出結果:

從輸出結果中,我們可以看到Mythread 和 Mythread2 兩個執行緒先後訪問getInstance()方法,Mythread 在訪問getInstance ()時,因為帶了引數 0, 所以getInstance()在還沒有建立單例例項時停頓了500毫秒。而在這短時間裡,Mythread2 趁虛而入,訪問 getInstance()方法,立馬生成一個單例例項,並返回給Mythread2 。之後500毫秒過了,Mythread所訪問的getInstance 執行緒甦醒,繼而執行 instance  = new SingletonFull() 方法,返回另外一個單例例項。

那麼換個思路,我是否可以將測試用的程式碼換成:

 SingletonFull sFull = SingletonFull.getInstance(0);
  SingletonFull sFull2 = SingletonFull.getInstance(1);
  System.out.println(sFull);
  System.out.println(sFull2);

結果顯示只生成了一個單例例項,為什麼呢?

前面提到了在SingletonFull 中的 Thread.currentThread.sleep(500),是使訪問Singleton 類的執行緒休眠,而不是讓SingletonFull 休眠,而且SingletonFull  不是執行緒類,無法休眠。

通過將getInstance() 修改成下面的程式碼,僅僅是添加了一個輸出當前訪問SingletonFull 類的執行緒名字。

接著改變測試程式碼,也添加了一條輸出當前執行緒名稱的程式碼

最後結果輸出:

從結果中可以看到在整個測試程式碼執行過程中,只有main 執行緒在執行。並且這裡,由於main執行緒先休眠了500毫秒,然後才依次打印出單例例項的名稱。而在前面的測試程式碼中,程式一執行,就先輸出了一個單例例項名稱,過了500毫秒後,才輸出另外一個單例例項名稱。