「曾經有乙份真摯的感情擺在我的面前我沒有珍惜,等我失去的時候才追悔莫及,人間最痛苦的事莫過於此,你的劍在我的咽喉上刺下去吧,不用在猶豫了!如果上天能給我一次再來一次的機會,我會對哪個女孩說三個字:我愛你,如果非要在這份愛上加乙個期限,我希望是一萬年!」
這是大話西遊中的經典台詞,而我們猿類世界的表白台詞是:
你就是我的單例,我的唯一!
我們回到猿類世界中,我們接下來看:
單例模式是比較常見的一種設計模式,目的是保證乙個類只能有乙個例項,而且例項化之後並向整個系統提供這個例項,避免例項的頻繁建立和釋放,節約記憶體,提高效率。
應用場景
許多時候整個系統只需要擁有乙個的全域性物件,這樣有利於我們協調系統整體的行為。
1、比如在某個伺服器程式中,該伺服器的配置資訊存放在乙個檔案中,這些配置資訊由乙個單例物件統一讀取,然後服務程序中的其他物件再通過這個單例物件獲取這些配置資訊。這種方式簡化了在複雜環境下的配置管理;
2、單例模式提供整個應用生命週期的上下文context;
3、計算機中的印表機也是採用單例模式設計的,乙個系統中可以存在多個列印任務,但是只能有乙個例項同步處理列印任務;
4、web頁面的計數器也是用單例模式實現的。
一般將單例模式的實現分為兩種,分別是餓漢式、懶漢式。
餓漢式:在類載入時就完成了初始化,所以類載入比較慢,但獲取物件的速度快。
懶漢式:在類載入時不初始化,等到第一次被使用時才初始化。
package com.test.singleton;public class singleton //對外方法public static修飾,直接通過singleton.getinstance()的方式獲取單例 public static singleton getinstance() }
這種方式是執行緒安全的,可用。是比較常見的寫法,在類載入的時候就完成了例項化,避免了多執行緒的同步問題。當然也有缺點,因為類載入時就例項化了,沒有達到lazy loading (懶載入) 的效果,如果該例項沒被使用,記憶體就浪費了。
package com.test.singleton;public class singleton2 //對外方法public static修飾,直接通過singleton2.getinstance()的方式獲取單例 public static singleton2 getinstance() return s; }}
這是懶漢式的一種寫法,只有在方法第一次被訪問時才會例項化,達到了懶載入的效果。但是這種寫法有個致命的問題,就是多執行緒的安全問題。假設物件還沒被例項化,然後有兩個執行緒同時訪問,那麼就可能出現多次例項化的結果,所以這種寫法不可採用。
package com.test.singleton;public class singleton3 //對外方法public static修飾,直接通過singleton3.getinstance()的方式獲取單例 //加上synchronized修飾,所有執行緒訪問該方法時同步訪問,可解決執行緒安全問題。 //缺點是鎖的粒度大,導致效率低下 public static synchronized singleton3 getinstance() return s; }}
這種寫法是對getinstance()加了鎖處理,保證了同一時刻只能有乙個執行緒訪問並獲得例項,但是缺點也很明顯,效率不高,因為synchronized是修飾整個方法,每個執行緒訪問都要進行同步,而其實這個方法只執行一次例項化**就夠了,每次都同步方法顯然效率低下,為了改進這種寫法,就有了下面的雙重檢查懶漢式。
package com.test.singleton;public class singleton4 //對外方法public static修飾,直接通過singleton4.getinstance()的方式獲取單例 public static singleton4 getinstance() } } return s; }}
這種寫法用了兩個if判斷,也就是double-check,並且同步的不是方法,而是**塊,效率較高,是對第三種寫法的改進。為什麼要做兩次判斷呢?這是為了執行緒安全考慮,還是那個場景,物件還沒例項化,兩個執行緒1和2同時訪問靜態方法並同時執行到第乙個if判斷語句,這時執行緒1先進入同步**塊中例項化物件,結束之後執行緒2也進入同步**塊,如果沒有第二個if判斷語句,那麼執行緒b也同樣會執行例項化物件的操作了。
但是此處有乙個潛在問題,變數s需要加上關鍵字volatile進行修飾。
/** * * 加上volatile,禁止指令重排,造成的bug。 * s = new singleton4();看似一句話,但實際上是乙個非原子操作,大致分為三個步驟: * 1、memory = allocate(); //1:分配物件的記憶體空間 * 2、ctorinstance(memory); //2:初始化物件 * 3、instance = memory; //3:設定instance指向剛分配的記憶體位址 * 但是經過指令重排序後如下: * 1、memory = allocate(); //1:分配物件的記憶體空間 * 2、instance = memory; //3:設定instance指向剛分配的記憶體位址,此時物件還沒被初始化,但是instance已經不為null了。如果此時來個乙個新的執行緒則會直接返回還沒有初始化的物件,進發生bug * 3、ctorinstance(memory); //2:初始化物件 * */private static volatile singleton4 s = null;
一旦乙個變數(類的成員變數、類的靜態成員變數)被volatile修飾之後,那麼就具備了兩層語義:
1)保證了不同執行緒對這個變數進行操作時的可見性,即乙個執行緒修改了某個變數的值,這新值對其他執行緒來說是立即可見的。
2)禁止進行指令重排序。
具體volatile的說明後續會再寫文章解釋。
package com.test.singleton;public class singleton5 private static class singletoninstance //對外方法public static修飾,直接通過singleton4.getinstance()的方式獲取單例 public static singleton5 getinstance() }
這是很多開發者推薦的一種寫法。
我們把singleton5例項放到乙個靜態內部類中,這樣可以避免靜態例項singleton5類的載入階段就建立物件。靜態變數初始化是在singletoninstance類初始化時觸發的,並且由於靜態內部類只會被載入一次, 所以這種寫法也是執行緒安全的。
考慮反射:由於在呼叫 singletoninstance.s 的時候,才會對單例進行初始化,而且通過反射,是不能從外部類獲取內部類的屬性的。所以這種形式,很好的避免了反射入侵。
考慮多執行緒:由於靜態內部類的特性,只有在其被第一次引用的時候才會被載入,所以可以保證其執行緒安全性。
優勢:兼顧了懶漢模式的記憶體優化(使用時才初始化)以及餓漢模式的安全性(不會被反射入侵)。
劣勢:需要兩個類去做到這一點,雖然不會建立靜態內部類的物件,但是其 class 物件還是會被建立,而且是屬於永久代的物件。建立的單例,一旦在後期被銷毀,不能重新建立。
歡迎朋友們關注**點讚,謝謝~~
大話設計模式 單例模式
在開發過程中,其實很多情況下,都需要用到單例模式來維持物件的唯一性。比如執行緒池 資料來源 sessionfactory等。一般的做法 懶漢式 public class myclass 宣告乙個靜態方法來返或乙個單例物件 public static myclass getinstance 但是這個會...
大話設計模式 單例模式
一 單例模式 保證乙個類僅有乙個例項,並提供乙個訪問它的全域性訪問點。二 例項 public class singleton public static singleton getinstance return singleton public class singletondousync publ...
大話設計模式之單例模式 Singleton
目錄定義 uml參與者示例 定義 單例模式的意思就是只有乙個例項。單例模式確保某乙個類只有乙個例項,而且自行例項化並向整個系統提供這個例項。這個類稱為單例類。應用 每台計算機可以有若干個印表機,但只能有乙個printer spooler,以避免兩個列印作業同時輸出到印表機中。每台計算機可以有若干傳真...