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

  前兩天寫了設計模式總綱,今天就來說講咱們在工程代碼中最最最經常使用的設計模式了——單例設計模式,這個模式在工程代碼上的出現率幾乎爲99.99999%,可是雖然很經常使用,可是用的好的人卻很少,今天咱們就來深刻的說一說單例設計模式。設計模式

  在學習一項新的知識以前,咱們都要向本身提出三個問題,爲何要用這個知識,這個知識用在哪裏,這個知識怎麼用?既 why,where,how,3W模式,咱們先來談談爲何須要單例設計模式,先來想一想,若是一個工具類,好比一個讀取配置文件的工具類,這類工具只是特定的功能類,既讀取指定的文件內容,這種類咱們在使用的時候只須要建造一個就好了,而後在整個系統之中都用這個類來進行指定文件的讀取便可,可是若是在設計之初沒有考慮好,並無把其設計成單例,致使整個系統中分佈多個相似的功能類,一方面,致使了系統資源的浪費,若是該配置文件內容較小,對內存來講還好,可是若是是幾百M或者幾個G的配置文件的內容的話,就會形成系統資源的嚴重浪費,致使內存泄露,一方面也會讓代碼顯得異常凌亂。安全

  爲了解決這種問題,既對於只是解決單一功能的功能類,咱們最好的作法就是將其設計成單例,接下來咱們來看看咱們要怎麼來實現一個單例。服務器

  正所謂萬丈高樓平地起,再複雜的功能也是由一行行簡單的代碼組成的,那咱們來看一下,要實現一個單例類的話,首先,確定是不能讓用戶自行生產的,那就是說明不能讓用戶new,因此,就必須把構造函數設置成爲私有的。多線程

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

好了,這就是單例了,哦,不,這應該是無例,由於把構造函數都弄成私有的了,什麼都沒有,用戶拿到了這個類只能一臉懵逼,既然要變成單例,那確定要給用戶一個實例是吧,既然用戶建立不了,那咱們就給他一個,以下jvm

 1 public class WorstLazySingleton {
 2     //一、私有化構造函數
 3     private WorstLazySingleton(){}
 4     
 5      //二、靜態化一個實例,靜態的實例保證了在每個類中都是惟一的
 6     private static WorstLazySingleton instance = null;
 7     
 8     
 9     //三、返回該對象的實例,也必須是靜態的方法,否則沒法調用靜態的實例
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     //一、私有化構造方法
 3     private NormalSingleton(){}
 4     
 5     //二、靜態化一個實例,靜態的實例保證了在每個類中都是惟一的
 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的最後一點資源,同步的重任就交給你了,(很用力的拍了拍虛擬機的肩膀),那咱們來講一下,何時虛擬機會本身給本身加同步。學習

  一、在靜態字段上或static{}塊中的初始化數據時測試

  二、訪問final字段時

  三、在建立線程以前建立對象時

  四、線程能夠能夠看見它將要處理的對象時

那有了這四個條件,咱們就能夠想象,要讓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,敬請期待。

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

相關文章
相關標籤/搜索