設計模式(一)之單例模式

單例模式(Singleton Pattern)

單例模式:Java 中最簡單的設計模式之一。這種類型的設計模式屬於建立型模式,它提供了一種建立對象的最佳方式。設計模式

特色: 緩存

  • 一、單例類只能有一個實例。
  • 二、單例類必須本身建立本身的惟一實例。
  • 三、單例類必須給全部其餘對象提供這一實例。

優勢:安全

  • 一、在內存裏只有一個實例,減小了內存的開銷,尤爲是頻繁的建立和銷燬實例(好比管理學院首頁頁面緩存)。
  • 二、避免對資源的多重佔用(好比寫文件操做)。

缺點:沒有接口,不能繼承,與單一職責原則衝突,一個類應該只關心內部邏輯,而不關心外面怎麼樣來實例化。多線程

具體實現

    (一)餓漢式

    寫法一(最經常使用的餓漢式寫法):

     描述:比較經常使用,但容易產生垃圾對象。
     優勢:JVM保證單例,不須要加鎖就能保證線程安全,執行效率會提升。
     缺點:在類加載時就進行實例化對象,浪費內存。併發

 1 public class SingletonPatternDemo1 {
 2 
 3     private static SingletonPatternDemo1 singleton = new SingletonPatternDemo1(); 4 5 private SingletonPatternDemo1(){ 6 7  } 8 9 public static SingletonPatternDemo1 getInstance(){ 10 return singleton; 11  } 12 13 }

    寫法二(經過靜態代碼塊實現):

        寫法一和寫法二本質是相同的,都是餓漢式的實現方式,只是代碼實現不一樣。spa

 1 public class SingletonPatternDemo2 {
 2     private static final SingletonPatternDemo2 INSTANCE; 3 4 static{ 5 INSTANCE = new SingletonPatternDemo2(); 6  } 7 8 private SingletonPatternDemo2(){ 9 10  } 11 12 public static SingletonPatternDemo2 getInstance(){ 13 return INSTANCE; 14  } 15 }

 

 (二)懶漢式

             寫法一:

      描述:針對餓漢式在類加載時就進行對象實例化,浪費內存的缺點進行改進,使在加載類時只是申明惟一的內對象,並不進行對象實例化;而在第一次使用時進行對象的實例化。線程

      優勢:達到了在須要時初始化的目的。設計

      缺點:多線程訪問時會存在問題,不能保證線程安全code

 1 public class SingletonPatternDemo3 {
 2     private static SingletonPatternDemo3 INSTANCE; 3 4 private SingletonPatternDemo3(){ 5 6  } 7 8 public static SingletonPatternDemo3 getInstance(){ 9 if (INSTANCE == null){ 10 INSTANCE = new SingletonPatternDemo3(); 11  } 12 return INSTANCE; 13  } 14 15 public static void main(String[] args){ 16 for (int i = 0 ; i < 100 ; i++){ 17 new Thread(() -> { 18  System.out.println(SingletonPatternDemo3.getInstance().hashCode()); 19  }).start(); 20  } 21  } 22 }
    /*這種方式是最基本的實現方式,這種實現最大的問題就是不支持多線程。由於沒有加鎖 synchronized,因此嚴格意義上它並不算單例模式。
     這種方式 lazy loading 很明顯,不要求線程安全,在多線程不能正常工做。*/

   寫法二:

      描述:對於寫法一雖然達到了使用時初始化可是線程不安全的特色,於是催生出了寫法二使用synchronized加鎖來保證線程安全。對象

      優勢:達到了在須要時初始化的目的,解決了多線程不安全的問題。

      缺點:使用了synchronized加鎖的方式來解決線程安全性問題,尤爲是在class中static方法上加鎖,極大地下降了代碼的效率。

 1 public class SingletonPatternDemo4 {
 2 
 3     private static SingletonPatternDemo4 INSTANCE; 4 5 private SingletonPatternDemo4(){ 6 7  } 8 9 public static synchronized SingletonPatternDemo4 getInstance(){ 10 if (INSTANCE == null){ 11 INSTANCE = new SingletonPatternDemo4(); 12  } 13 return INSTANCE; 14  } 15 16 public static void main(String[] args){ 17 for (int i = 0 ; i < 100 ; i++){ 18 new Thread(() -> { 19  System.out.println(SingletonPatternDemo4.getInstance().hashCode()); 20  }).start(); 21  } 22  } 23 24 }

     寫法三:

      描述:在寫法二中經過加鎖的方式解決了多線程安全性的問題,但同時因爲加鎖位置致使代碼效率極低;於是改變加鎖的位置是否就能知足線程安全又能挽救一部分代碼效率呢?大膽的作出假設是否再也不靜態類方法上進行加鎖,改在須要保障線程安全的代碼塊上加鎖,是否是就能既能保障線程安全也能提供代碼效率,催生出第三種在實例化對象的代碼塊上進行加鎖。

      優勢:達到了在須要時初始化的目的,必定程度上提升了代碼效率。

      缺點:事實證實並不能保障線程安全。

 1 public class SingletonPatternDemo5 {
 2     private static SingletonPatternDemo5 INSTANCE; 3 4 private SingletonPatternDemo5(){ 5 6  } 7 8 public static SingletonPatternDemo5 getInstance(){ 9 if (INSTANCE == null){ 10 //妄圖經過減小同步代碼塊的方式提升效率,然而不可行 11 synchronized(SingletonPatternDemo5.class) { 12 INSTANCE = new SingletonPatternDemo5(); 13  } 14  } 15 return INSTANCE; 16  } 17 18 public static void main(String[] args){ 19 for (int i = 0 ; i < 100 ; i++){ 20 new Thread(() -> { 21  System.out.println(SingletonPatternDemo5.getInstance().hashCode()); 22  }).start(); 23  } 24  } 25 }

    經過分析代碼:發現存在問題是由於當線程A和線程B併發執行,先是線程A執行到if(INSTANCE == null){}判斷並知足條件,此時線程B也執行到此判斷,可是線程A正在申請或剛剛申請到鎖尚未執行到實例化對象,那麼線程B的判斷也是true,並進入等待鎖的釋放,於是線程A和B都會執行實例化對象,就會產生兩個對象。因此這種寫法不能保證線程安全。

              也行存在這樣的疑或,爲何不把判斷放進synchronized代碼塊中,這樣就能保證線程安全。這種寫法與寫法二並無本質的不一樣。

           寫法四(雙檢鎖/雙重校驗鎖(DCL,即 double-checked locking)------之前認爲最完美的方式):

      描述:經過寫法三以及寫法三的分析,在synchronized加鎖先後都進行判斷就能知足要求,於是誕生了雙重判斷的寫法。

      優勢:達到了在須要時初始化的目的,必定程度上提升了代碼效率,線程安全。

      缺點:使用了synchronized加鎖就有效率的損失,並且代碼也愈來愈複雜。

 1 public class SingletonPatternDemo6 {
 2 
 3     private static volatile SingletonPatternDemo6 INSTANCE;//加上volatile是由於須要解決在進行JIT設置時存在的語句重排問題
 4 
 5     private SingletonPatternDemo6(){ 6 7  } 8 9 public static SingletonPatternDemo6 getInstance() { 10 if (INSTANCE == null) {//第一個判斷有必要嗎?有必要,當一些線程判斷到INSTANCE!=null時就返回了,不用都去競爭這把鎖,提升代碼效率 11 //雙重判斷 12 synchronized (SingletonPatternDemo6.class){ 13 if (INSTANCE == null) { 14 INSTANCE = new SingletonPatternDemo6(); 15  } 16  } 17  } 18 return INSTANCE; 19  } 20 21 public static void main(String[] args){ 22 for (int i = 0 ; i < 100 ; i++){ 23 new Thread(() -> { 24  System.out.println(SingletonPatternDemo6.getInstance().hashCode()); 25  }).start(); 26  } 27  } 28 29 }

   (三)登記式 / 靜態內部類實現

      描述:這種方式能達到雙檢鎖方式同樣的功效,但實現更簡單。對靜態域使用延遲初始化,應使用這種方式而不是雙檢鎖方式。這種方式只適用於靜態域的狀況,雙檢鎖方式可在實例域須要延遲初始化時使用。

      優勢:JVM保證單例,不存在多線程不安全問題,加載外部類的時候不會加載內部類,能夠實現懶加載,可以在使用時才進行實例化。

 1 public class SingletonPatternDemo7 {
 2 
 3     public SingletonPatternDemo7(){ 4 5  } 6 7 private static class SingletonPatternDemo7Holder{ 8 private static final SingletonPatternDemo7 INSTANCE = new SingletonPatternDemo7(); 9  } 10 11 public static SingletonPatternDemo7 getInstance() { 12 return SingletonPatternDemo7Holder.INSTANCE; 13  } 14 15 public static void main(String[] args){ 16 for (int i = 0 ; i < 100 ; i++){ 17 new Thread(() -> { 18  System.out.println(SingletonPatternDemo7.getInstance().hashCode()); 19  }).start(); 20  } 21  } 22 }

   (四)枚舉實現

      描述:這種方式是JAVA創始人之一, 《Effective Java》的做者 Josh Bloch 提倡的方式,它不只能避免多線程同步問題,並且還自動支持序列化機制,防止反序列化從新建立新的對象,絕對防止屢次實例化,但在實際中基本不用,由於這須要把class類改成enum類。

      優勢:不只保證了多線程的問題,還能防止反序列化。

 1 public enum SingletonPatternDemo8 {
 2 
 3  INSTANCE; 4 5 public static void main(String[] args){ 6 for (int i = 0 ; i < 100 ; i++){ 7 new Thread(() -> { 8  System.out.println(SingletonPatternDemo8.INSTANCE.hashCode()); 9  }).start(); 10  } 11  } 12 }

寫在最後:通常狀況下,使用餓漢式-寫法一就能夠了。只有在要明確實現 lazy loading 效果時,纔會使用(三)登記式 / 靜態內部類實現的方式。若是涉及到反序列化建立對象時,能夠嘗試使用(四)枚舉實現的方式。若是有其餘特殊的需求,能夠考慮使用 懶漢式-寫法四(雙檢鎖/雙重校驗鎖(DCL,即 double-checked locking))的方式。

相關文章
相關標籤/搜索