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毫秒後,才輸出另外一個單例例項名稱。