常常工做面試的時候,面試官會問,怎麼保證某個類在程序運行過程當中只有一個對象存在?面試
以上問題,實際就是面試官在瞭解面試者對單例模式的瞭解。緩存
以常見的使用計算機打印文件爲例,假設計算機擁有A,B兩臺鏈接的打印機,如後臺打印程序也有兩個的話,可能形成以下問題:安全
當打印文檔的時候,由於打印程序也有多個,可能形成一份完整文檔同時被兩個打印程序同時進行分配,致使A,B打印機互相不知道在打印什麼,致使混亂。多線程
因此,每臺計算機能夠有若干個打印機,但只能有一個後臺打印程序,以免兩個打印做業同時輸出到打印機中。每臺計算機能夠有若干通訊端口,系統應當集中管理這些通訊端口,以免一個通訊端口同時被兩個請求同時調用。併發
在計算機系統中,線程池、緩存、日誌對象、對話框、打印機、顯卡的驅動程序對象常被設計成單例。這些應用都或多或少具備資源管理器的功能。app
總之,選擇單例模式就是爲了不不一致狀態,避免政出多頭。ide
1. 單例模式只能有一個實例。高併發
2. 單例類必須建立本身的惟一實例。spa
3. 單例類必須向其餘對象提供這一實例。線程
1.餓漢式單例
2.懶漢式單例
3.登記式單例
登記式單例是爲了解決模式1,2不能繼承的問題而開發的,自己存在爭議,暫不討論。
1)既然在程序中只能建立一個對象,那也就是說在不能在其它類中任意建立對象,不然對象確定就不止一個,這就要求被建立那個類的構造方法不能public,也就是必須private,這個就保證了外部不能亂建立對象。
2)外面建立不了對象,也就只能在內部建立對象了,由於咱們講究Java的封裝特性,對象是private,因此咱們要提供一個public方法供外界調用。
3)由於在外面不可能建立對象,想調用方法就必須將方法static,這樣就能夠經過:類名.方法名獲取對象了,又由於靜態方法裏只能用靜態成員,因此single必須static化。
4)至此,建立一個對象的任務完成了,通常狀況下,咱們另外在static加個final,
最終程序以下
餓漢式是線程安全的,在類建立的同時就已經建立好一個靜態的對象供系統使用,之後不在改變無需關注多線程問題、寫法簡單明瞭、能用則用。
可是它是加載類時建立實例、因此若是是一個工廠模式、緩存了不少實例、那麼就得考慮效率問題,由於這個類一加載則把全部實例無論用不用一塊建立。
之因此叫作懶漢模式,主要是由於此種方法能夠很是明顯的lazy loading,簡單說就是何時用,何時建。
然而併發實際上是一種特殊狀況,大多時候這個鎖佔用的額外資源都浪費了,這種方式寫出來的結構效率很低。
解釋一下在併發時,雙重校驗鎖法會有怎樣的情景:
STEP 1. 線程A訪問getInstance()方法,由於單例尚未實例化,因此進入了鎖定塊。
STEP 2. 線程B訪問getInstance()方法,由於單例尚未實例化,得以訪問接下來代碼塊,而接下來代碼塊已經被線程1鎖定。
STEP 3. 線程A進入下一判斷,由於單例尚未實例化,因此進行單例實例化,成功實例化後退出代碼塊,解除鎖定。
STEP 4. 線程B進入接下來代碼塊,鎖定線程,進入下一判斷,由於已經實例化,退出代碼塊,解除鎖定。
STEP 5. 線程A初始化並獲取到了單例實例並返回,線程B獲取了在線程A中初始化的單例。
理論上雙重校驗鎖法是線程安全的,而且,這種方法實現了lazyloading。
Volatile關鍵字的做用: 禁止進行指令的重排序
使用內部類的好處是,靜態內部類不會在單例加載時就加載,而是在調用getInstance()方法時才進行加載,達到了相似懶漢模式的效果,而這種方法又是線程安全的。
Effective Java做者Josh Bloch 提倡的方式,簡潔而完美。解決了如下三個問題:
(1)自由序列化。
(2)保證只有一個實例。
(3)線程安全。
若是咱們想調用它的方法時,僅須要如下操做:
推薦使用餓漢式和枚舉實現單例模式,餓漢式徹底不用考慮線程安全,枚舉則是簡單優雅;懶漢式的問題則是當沒有高併發的狀況下,白白浪費資源,效率低下;並且,若是建立對象時初始化的工做較多的話,可能會將這部分時間轉嫁給用戶,不太友好。