本文由@呆代待殆原創,轉載請註明出處。 單例模式簡述 單例模式保證了我們的類只有一個實例,並且我們在任何時候都可以取得這個實例,其中保證我們的類有且僅有一個實例在某些時候是相當重要的事情,比如我們只需要一個線程池而不是兩個等等,但是我們也要註意,單例模式適用的情況比我們想象中的要少,所以請不要濫用這 ...
本文由@呆代待殆原創,轉載請註明出處。
單例模式簡述
單例模式保證了我們的類只有一個實例,並且我們在任何時候都可以取得這個實例,其中保證我們的類有且僅有一個實例在某些時候是相當重要的事情,比如我們只需要一個線程池而不是兩個等等,但是我們也要註意,單例模式適用的情況比我們想象中的要少,所以請不要濫用這個模式。
單例模式具有的一些特征
1,單例模式保證了我們的程式中有且僅有一個實例的存在。
2,我們在任何時候都能取得這個實例。
3,單例模式的構造方法是私有的,所以在不破壞這一私有條件的情況下單例類是不能作為父類存在的。
單例模式的定義與基本結構
單例模式只有一個類而已,所以實際上並不存在結構這一說= =,但是我們還是可以看一下它的定義。
定義:確保類有且僅有一個實例,並保證任何時候都能訪問這個實例。(這句話好像已經出現了好的次 = =)
單例模式的定義與結構都非常簡單,理解起來甚至不需要舉額外的例子,但是,真正去實現單例的時候我們還是有很多細節要註意的,那麼下麵我們就在實際的代碼中繼續研究吧。
單例模式的代碼實現(Java版)
代碼實現
1 public class MySingleten { 2 public static MySingleten instance=null;//指向實例的變數 3 private MySingleten(){}//私有化構造函數,然別的代碼無法創建這個類的實例 4 public static MySingleten getInstance(){//我們取得這個單例的方法。 5 if(null==instance){ 6 instance=new MySingleten(); 7 } 8 return instance; 9 } 10 public void MyFunction(){//一般方法的代表 11 System.out.println("我是單例,這是我的方法"); 12 } 13 }
這種實現方式我們一般叫它:懶漢式
為什麼呢?因為它直到第一次被調用的時候才會生成自己的實例(就像我們每次都要到交作業的時候才會開始寫作業一樣,總之就是懶唄= ̄ω ̄=)
註意:懶漢式是線程不安全的
舉例:想象一下情況,線程A執行到代碼的第5行,判斷成功,在準備進入第6行的時候CPU切換了,線程B恰好也執行這段代碼,這時很明顯instance還是==null的浴室線程B成功創建了一個instance的實例,結果,當CPU又切回線程A時,麻煩來了,線程A繼續執行第6行代碼又創建了一個instance的實例,並把原來的那個覆蓋了,這就很有可能導致意想不到的問題。
所以,我們必須想辦法解決這個問題,這裡我們提供以下幾種思路。
餓漢式:不再等到要使用的時候才創建,而是在程式開始的時候就創建好(對這個實例很饑渴的樣子,所以叫餓(chi)漢式)
1 public class MySingleten { 2 public static MySingleten instance=new MySingleten();//一開始就生成這個變數就不存線上程問題了 3 private MySingleten(){} 4 public static MySingleten getInstance(){ 5 return instance; 6 } 7 public void MyFunction(){ 8 System.out.println("我是單例,這是我的方法"); 9 } 10 }
synchronized方法:直接在懶漢式的getInstance方法前加上synchronized修飾符,這樣就能解決線程安全問題了,這個解決方法是最簡單的,但是效率卻非常低下,因為只有第一次創建實例的時候這個synchronized是有必要的,當實例創建完成後,這個synchronized就只剩下拖慢速度的作用了。
1 public class MySingleten { 2 public static MySingleten instance=null; 3 private MySingleten(){} 4 public static synchronized MySingleten getInstance(){ 5 if(null==instance){ 6 instance=new MySingleten(); 7 } 8 return instance; 9 } 10 public void MyFunction(){ 11 System.out.println("我是單例,這是我的方法"); 12 } 13 }
雙重加鎖方法:在懶漢式的基礎上,我們可以用兩把鎖來分別控制單例的取得和創建,因為只需要在第一次創建單例的時候註意線程安全問題,那麼,我們在內層鎖上用synchronized來控制,在外層鎖上用 if(null==instance) 來判斷是否存在這個實例,這樣就省去了synchronized在後來浪費的同步時間
1 public class MySingleten { 2 public volatile static MySingleten instance=null;//註意這裡增加了volatile關鍵字 3 private MySingleten() {} 4 public static MySingleten getInstance() { 5 if (null == instance) {// 外層鎖,判斷是否實例已經被創建 6 synchronized (MySingleten.class) {// 內層鎖控制線程間同步,實例被創建後就沒有運行的機會了,省去了多餘的線程間同步成本 7 if(null==instance)//需要再次檢查,因為很有可能線程A在這裡時,線程B已經通過外層的if了。 8 instance = new MySingleten(); 9 } 10 } 11 return instance; 12 } 13 public void MyFunction() { 14 System.out.println("我是單例,這是我的方法"); 15 } 16 }
靜態全局變數和單例模式的對比
1,靜態全局變數並不能保證對象是唯一的(既然你能創建這個靜態全局變數就說明這個類的構造函數並不是私有的)。
2,多餘的全局變數會造成命名空間的污染。
3,全局變數總是存在,會一直占用記憶體,而單例模式可實現訪問的時候再創建單例的實例。
4,單例模式產生的對象保存在堆里,但是靜態全局變數保存在棧里。
靜態成員和單例模式的對比
1,用都是靜態成員的類去模擬單例的話,它是不能實現別的介面的,這種用法脫離了面向對象的思想(除非這個類的應用與實現不需要面向對象的思想那麼你可以這麼做)。
2,靜態成員可以選擇性的將類裡面的東西分成需要保證唯一性的和不需要保證唯一性的,某些時候更加靈活。
java版本相容性提醒
1,Java1.2之前的垃圾回收機制是有bug的,會造成當單例實例在沒有全局引用的情況下被清除掉。
2,Java1.4之前許多JVM對於volatile的實現會導致雙重加鎖的方法失效
關於單例模式的一些爭議
如果你剛剛看完了我寫的博文並覺得又有了一點收穫而感到很開心的話(如果真的是這樣那我也會很開心的♪(^∇^*)),我覺得你可以先冷靜一下(大霧= =),單例設計模式在網路上是一個很有爭議的模式,有人覺得這個模式違反了太多的設計原則,有人覺他增加了代碼的耦合性等等,但有的時候我們又確實需要單例帶給我們的一些特性,本來博主想多看一些別人的文章後幫大家總結一下網路上關於這方面的討論,結果博主發現這些討論實在是太多了,而且比較雜,各種聲音都有,stackoverflow上有個類似的問題以"這個問題的回答是基於個人選擇而不是基於經驗的..."而被關閉了,所以博主覺得還是選一些博主覺得比較好的連接給各位,讓各位自行斟酌比較好。(相關討論的連接都放在了參考資料里,如果覺得不夠的話,可以直接google一下"singleton why bad")
參考資料:
1,《Head First 設計模式》
2,https://agiletribe.wordpress.com/2013/10/08/dont-abuse-singleton-pattern/ 關於何時才應該使用單例模式
3,https://www.cnblogs.com/seesea125/archive/2012/04/05/2433463.html關於為什麼不用靜態方法而要用單例模式
4,https://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons關於單例模式爭議的討論
5,https://stackoverflow.com/questions/519520/difference-between-static-class-and-singleton-pattern關於單例模式和靜態類之間的討論