設計模式總綱——單例設計模式

來源:互聯網
上載者:User

標籤:

  前兩天寫了設計模式總綱,今天就來講講我們在工程代碼中最最最常用的設計模式了——單例設計模式,這個模式在工程代碼上的出現率幾乎為99.99999%,但是雖然很常用,但是用的好的人卻不多,今天我們就來深入的說一說單例設計模式。

  在學習一項新的知識之前,我們都要向自己提出三個問題,為什麼要用這個知識,這個知識用在哪裡,這個知識怎麼用?既 why,where,how,3W模式,我們先來談談為什麼需要單例設計模式,先來想想,如果一個工具類,比如一個讀取設定檔的工具類,這類工具只是特定的功能類,既讀取指定的檔案內容,這種類我們在使用的時候只需要建造一個就行了,然後在整個系統之中都用這個類來進行指定檔案的讀取即可,但是如果在設計之初沒有考慮好,並沒有把其設計成單例,導致整個系統中分布多個類似的功能類,一方面,導致了系統資源的浪費,如果該設定檔內容較小,對記憶體來說還好,但是如果是幾百M或者幾個G的設定檔的內容的話,就會造成系統資源的嚴重浪費,導致記憶體泄露,一方面也會讓代碼顯得異常淩亂。

  為瞭解決這種問題,既對於只是解決單一功能的功能類,我們最好的做法就是將其設計成單例,接下來我們來看看我們要怎麼來實現一個單例。

  正所謂萬丈高樓平地起,再複雜的功能也是由一行行簡單的程式碼群組成的,那我們來看一下,要實現一個單例類的話,首先,肯定是不能讓使用者自行生產的,那就是說明不能讓使用者new,所以,就必須把建構函式設定成為私人的。

1 public class Singleton {2     private Singleton(){}3 }

好了,這就是單例了,哦,不,這應該是無例,因為把建構函式都弄成私人的了,什麼都沒有,使用者拿到了這個類只能一臉懵逼,既然要變成單例,那肯定要給使用者一個執行個體是吧,既然使用者建立不了,那我們就給他一個,如下

 1 public class WorstLazySingleton { 2     //1、私人化建構函式 3     private WorstLazySingleton(){} 4      5      //2、靜態化一個執行個體,靜態執行個體保證了在每一個類中都是唯一的 6     private static WorstLazySingleton instance = null; 7      8      9     //3、返回該對象的執行個體,也必須是靜態方法,不然無法調用靜態執行個體10     public static WorstLazySingleton getInstance(){11         if(instance == null){12             instance = new WorstLazySingleton();13         }14         return instance;15     }16 }

好了,一個新鮮的單例就出爐了,but,是不是有什麼問題呢,為什麼這個單例被加上了個Worst的標籤,這個年代什麼最慘,被人隨意貼標籤最慘,隔著螢幕都能感受到這個單例哀怨的眼神,但是,我們來看一看,這個單例,咋一看在單線程的環境下沒問題,但是只要一到了多線程的環境下,妥妥的要出問題啊,隨意寫了個測試案例,跑了個10條線程來getInstance,竟然出現了4個不一樣的hashCode,這個哪裡是單例,明顯是多的不能再多的”多例“了,好吧,這個worst的標籤就先貼上去吧。那有同學就說了,我加同步方法啊,好,我們來為這個類加上同步方法,

  大致如下代碼,

 1 public class BadLazySingleton { 2     private static BadLazySingleton instance = null; 3      4     private BadLazySingleton(){}
5 //加上了synchronized的同步方法 6 public static synchronized BadLazySingleton getInstance(){ 7 if(instance == null){ 8 instance = new BadLazySingleton(); 9 }10 return instance;11 }12 }

 

這個方法現在被加上了synchronized了,運行一下多線程的測試環境,咋一看,好像沒問題了,但是,我們再想一下下面的情境,如果在方法裡面這個對象特別大的話,導致虛擬機器調用的時間較長,或者在這個方法裡面做了其他的 doSomething()方法的話,那其他線程只能乖乖的等待他的結束了,比如這個 方法執行時間用了10S,那10條線程過來,想想就有點小激動呢,一旦運行在伺服器端上,那客戶的等待時間,流失率是妥妥的,又有同學要提意見了,我們可以來縮小範圍啊,我們不要再在方法上加同步了,好,那我們來看一看下個version的單例,

 1 public class NormalSingleton { 2     //1、私人化構造方法 3     private NormalSingleton(){} 4      5     //2、靜態化一個執行個體,靜態執行個體保證了在每一個類中都是唯一的 6     private static NormalSingleton instance = null; 7      8     public static NormalSingleton getInstance(){ 9         if(instance == null){10             //在局部進行同步,減少線程的等待時間11             synchronized (NormalSingleton.class) {12                 //進行雙重判斷,防止線程到了這一步剛好停住了,導致沒有繼續往下走而另外一條線程剛好進來13                 if(instance == null){14                     instance = new NormalSingleton();15                 }16             }17         }18         return instance;19     }20 }

看來這個版本是比較Normal的Singleton了,不僅進行了同步,而且只需要進行一次同步,即只需要在第一次進行同步即可,還涉及到了雙重判斷,防止多線程上環境上的串線,這就是所謂的 Double-Check,but,有人就想到,為什麼要我們要自己寫同步,有的人表示已經累覺不愛了,不喜歡自己寫同步了,要榨乾JVM的最後一點資源,同步的重任就交給你了,(很用力的拍了拍虛擬機器的肩膀),那我們來說一下,什麼時候虛擬機器會自己給自己加同步。

  1、在靜態欄位上或static{}塊中的初始化資料時

  2、訪問final欄位時

  3、在建立線程之前建立對象時

  4、線程可以可以看見它將要處理的對象時

那有了這四個條件,我們就可以想象,要讓JVM自動來實現同步的話,就可以採用靜態初始化器的方式,但是有人就會說了。靜態初始化器雖然是同步的,但是類一載入的時候他就會去初始化這個對象了,哪怕我們不需要他也會去載入這些對象,那接下來來個腦經急轉彎了,那如果我們可以讓這個類在載入的時候不要去初始化這個對象不就可以嘍?有人會說,有這等好事???

還真有,這種類就叫做靜態內部類,也叫作類級內部類,我們來看看代碼:這種方法算是目前最好的方法之一了:(為什麼叫之一....因為還有之二....)

 1 public class BestLazySingleton { 2     //私人化構造方法 3     private BestLazySingleton(){} 4      5     //建立靜態內部類,讓JVM自身來保證線程的安全性,而且該類只有在被調用到的時候才會去載入 6     private static class SingletonHolder { 7         private static BestLazySingleton instance = new BestLazySingleton(); 8     } 9     10     public static BestLazySingleton getInstance(){11         return SingletonHolder.instance;12     }13 }

這個類算是目前最好的懶載入的單例範本了,使用類級內部類,通過jvm來進行同步,當我們調用的時候才去初始化,進而實現了消極式載入的特性,而且也沒有增加同步方法塊,只增加了一個內部域的訪問,成本相較前面的幾種方法都非常低。

最後我們來講講目前最好的單例的方法之二,這個方法是在《Effective Java》書中提到的,通過Enum來實現單例,首先我們需要瞭解一個大前提,Java中的Enum實質上也是一個功能齊全的類,也可以有自己的屬性和方法,而且枚舉算是單例的泛型化,本質上是單元素的枚舉,而且也可以通過Enum來實現可變的多例類型的“單例”,具體代碼如下

 1 public enum EnumSingleton { 2     //定義一個枚舉的元素,就代表了Singleton的一個執行個體 3     instance; 4      5     private String singletonData; 6      7     public String getEnumSingleton(){ 8         return singletonData; 9     }10     11     public void setEnumSingleton(String singletonData){12         this.singletonData = singletonData;13     }14 }

也可以類似的寫上 instance2,instance3.......對於Enum來說,都是單例,這種實現形式基於JDK1.5以及JDK1.5以上

最後假設你不想使用懶載入的單例模型,你實在表示很想偷懶,那就使用餓漢式的單例吧,這種方法簡單粗暴,並且是安全執行緒的,就是類一旦被載入的時候就會去執行個體化該對象,哪怕不使用該類的時候,具體代碼如下:

 1 public class EagerSingleton { 2     //直接執行個體化類執行個體,其他別無二致 3     private static EagerSingleton instance = new EagerSingleton(); 4      5     private EagerSingleton(){} 6      7     public static EagerSingleton getInstance() { 8         return instance; 9     }10 }

這種方法簡單粗暴,老少鹹宜,但是效能如何就見仁見智了,

 

好了,差不多晚上的JAVA單例設計模式就講到這裡了,最後貼上思維導圖一張,就在總綱的基礎上在Singletong的設計模式上添加的,下回我們再見,下回我們具體會講到下一個CreationPattern中的Factory Method,敬請期待。

  如需轉載請告知,轉載請註明出處。

設計模式總綱——單例設計模式

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.