Head First Design Mode(6)-單件模式
該系列文章繫個人讀書筆記及總結性內容,任何組織和個人不得轉載進行商業活動!
單件模式:
獨一無二的物件;
單件模式(Singleton Pattern):用來建立獨一無二的,只能有一個例項的物件的入場券;
單件模式的類圖可以說是最簡單的,只有一個類,儘管從類設計的視角來說他很簡單,但是實現上還是會遇到不少問題;
一些只需要一個的物件:
比如 執行緒池 快取 對話方塊 日誌物件等,很大的可能是這些類只能有一個例項,如果製造出多個例項,就會導致很多問題;
使用單件模式實現類似的需求,可以確保只有一個例項會被建立,單件模式會提供一個全域性的訪問點,和全域性變數一樣方便,有沒有全域性變數的缺點;
比如將物件賦值給一個全域性變數,就必須在程式一開始就建立好物件;但如果這是一個非常消耗資源的過程,而程式又遲遲用不到這個物件,就會形成浪費,最好在需要時才建立物件;
建立一個單件模式:
將構造器私有,只有類的內部可以使用;
MyClass1有一個靜態方法,就是我們熟悉的類方法,使用類名呼叫;
在靜態方法中呼叫私有的構造器;
剖析經典的單件模式實現:
利用一個靜態變數來記錄Singleton類的唯一例項;
uniqueInstance一開始並沒有建立,當需要的時候才會產生,這就是“延遲例項化”(lazy instantiaze);
構造器宣告為私有,只有Singleton類內才可以呼叫該構造器;
使用getInstance()方法例項化物件,並返回這個例項;
當然,Singleton也是一個正常的類,可以具有其他用途的例項變數和方法;
單件模式構建物件:
讓任何時刻只有一個物件,確保程式中使用的全域性資源只有一份;
常被用來管理共享的資源,如資料庫連線或執行緒池;
由於構造器是私有的,任何人想獲取例項,就需要請求得到,而不可以自行例項化得到;
定義單件模式:
單件模式確保一個類只有一個例項,並提交一個全域性訪問點;
想要取得單件例項,通過單件類是唯一途徑;當需要例項時,向類查詢,它會返回單個例項;
類圖:
這個類針對沒問題嗎——比如在多執行緒的環境中;
當JVM使用兩個執行緒執行單例例項化方法時,可能都通過了uniqueInstance == null的判斷,這將導致靜態變數被初始化兩次,在一個物件初始化返回之後,另一個變數有被初始化付給了uniqueInstance並返回了,最終兩個執行緒返回了兩個不同的例項物件;
處理多執行緒:
只要把getInstance()變成同步(synchronized)方法,多執行緒的問題就可以解決了;
關鍵字synchronized修飾的方法,JVM會迫使每個執行緒進入這個方法之前,需要先等候別的執行緒離開該方法,即兩個執行緒不會同時進入這個方法;
使用synchronized修飾getInstance()方法的問題:
只有在第一次執行此方法時,才真正需要同步;一旦設定了uniqueInstance變數,就不再需要同步這個方法了;
之後每次呼叫這個方法,同步都是一種累贅;
改善多執行緒:
為了符合多數Java程式,我們需要確保單件模式能在多執行緒下正常工作;
顯然只使用同步getInstance()的做法並不行,當然如果對效能要求不高,那就什麼也別做了,這樣剛好;
“急切”建立例項:
如果我們的應用程式在建立和執行方便負擔不重,可以考慮在建立靜態變數uniqueInstance的同時對其進行初始化操作,這樣就可以保證執行緒安全了;
利用這種做法,JVM會在載入類的時候馬上建立此唯一的單件例項,先於任何執行緒訪問;
雙重檢查加鎖:
用“雙重檢查加鎖”(double-checked locking),在getInstance()中減少使用同步;
首先檢查例項是否已經建立,未建立才進行同步(同步區塊);這樣就只有第一次會同步;
這個做法會大大減少getInstance()的時間消耗;
volatile關鍵字:
在 JDK1. 5 之後,volatile確保黨uniqueInstance變數被初始化成Singleton例項時,多個執行緒正確的處理,保證 sInstance 物件每次都是從主記憶體中讀取;
“觀察加入volatile關鍵字和沒有加入volatile關鍵字時所生成的彙編程式碼發現,加入volatile關鍵字時,會多出一個lock字首指令”
lock字首指令實際上相當於一個記憶體屏障(也成記憶體柵欄),記憶體屏障會提供3個功能:
1)它確保指令重排序時不會把其後面的指令排到記憶體屏障之前的位置,也不會把前面的指令排到記憶體屏障的後面;即在執行到記憶體屏障這句指令時,在它前面的操作已經全部完成;
2)它會強制將對快取的修改操作立即寫入主存;
3)如果是寫操作,它會導致其他CPU中對應的快取行無效。
synchronized關鍵字是防止多個執行緒同時執行一段程式碼,那麼就會很影響程式執行效率,而volatile關鍵字在某些情況下效能要優於synchronized,但是要注意volatile關鍵字是無法替代synchronized關鍵字的,因為volatile關鍵字無法保證操作的原子性。
是否可以建立一個類把所有的方法都定義成靜態的,把類當成一個單件?
靜態初始化的控制權在Java手上,多個靜態變數的初始化順序如果有要求就會出現問題;
類載入器的問題:
兩個類載入器具有不同名稱空間,不同的類載入器載入同一個類的話,確實會被載入多次;
這種情況下的單件請自行指定同一類載入器,小心使用;
單件類是否可繼承:
如果想要繼承單件類,就需要把構造器公開;那就不是一個真正的單件了;
而且,子類會和父類共享靜態的例項變數,這可能不是你想要的;
相比單件模式,全域性變數也可以提供全域性訪問,但是不能確保只有一個例項;多個全域性變數還有可能造成名稱空間的汙染;
當然單例模式也有可能被濫用;通常使用單件模式的機會不多;
總結:
1.單件模式確保程式中一個類最多隻有一個例項;
2.單件模式也提供訪問這個例項的全域性點;
3.在Java中實現單例模式需要私有的構造器、一個靜態方法和一個靜態變數;
4.確定在效能和資源上的限制,小心選擇方案實現單件,已解決多執行緒問題;
5.JDK1.5之後,雙重檢查加鎖實現才有效;
6.小心,使用多個類載入器,可能會導致單件失效而產生多個例項;
OO基礎:
抽象;
封裝
繼承;
多型;
OO原則:
封裝變化
多用組合,少用繼承
針對介面程式設計,不針對實現程式設計
為互動物件之間的鬆耦合設計而努力;
類應該對擴充套件開放,對修改關閉;
依賴抽象,不要依賴具體類;
OO模式:
策略模式:定義演算法族,分別封裝起來,讓他們之間互相替換,此模式讓演算法的變化獨立於使用演算法的客戶;
觀察者模式:在物件之間定義一對多的依賴,這樣一來,當一個物件改變狀態,依賴它的物件都會收到通知,並自動更新;
裝飾者模式:動態地將責任附加到物件上;想要擴充套件功能,裝飾者提供有別於繼承的另一種選擇;
簡單工廠模式:
工廠方法模式:定義了一個建立物件的介面,但由子類決定要例項化的類是哪一個;工廠方法讓類把例項化推遲到子類;
抽象工廠模式:提供一個介面,用於建立相關或依賴物件的家族,而不需要明確具體的類;
——單件模式:確保一個類只有一個例項,並提供全域性訪問點;
思考:
OC及Swift的單例模式如何實現?