設計模式之單例模式


一、介紹

  單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。這種類型的設計模式屬於創建型模式,它提供了一種創建對象的最佳方式。這種模式涉及到一個單一的類,該類負責創建自己的對象,同時確保只有單個對象被創建。這個類提供了一種訪問其唯一的對象的方式,可以直接訪問,不需要實例化該類的對象。

TIPS:
    1、單例類只能有一個實例。
    2、單例類必須自己創建自己的唯一實例。
    3、單例類必須給所有其他對象提供這一實例。

 

實現的目標:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。

解決得問題:一個全局使用的類頻繁地創建與銷毀。

核心思想:構造函數是私有的。並且對外提供一個全局的訪問方法。

使用場景: 

1、要求生產唯一序列號。
2、代碼級別的全局的JVM緩存。(有狀態的單例)
3、創建的一個對象需要消耗的資源過多,比如 I/O 與數據庫的連接等。

 

二、懶漢式

  所謂的懶漢式單例,是指:第一次調用才初始化,避免內存浪費。典型的時間換取空間的做法。因為第一次獲取單例的時候會執行單例的實例化,浪費一些時間。但是后面再繼續獲取就不存時間問題了。

2.1、懶漢式的簡化版

 1 package com.shf.mode.singleton;
 2 
 3 /**
 4  * 描述:懶漢式
 5  * 單例實例在第一次調用的時候創建
 6  * 典型的以時間換取空間,第一次調用 getInstance()才會實例化單例。
 7  *
 8  * @Author shf
 9  * @Date 2019/7/15 16:12
10  * @Version V1.0
11  **/
12 public class SingletonExample2 {
13     // 私有構造方法,禁止外部直接創建實例
14     private SingletonExample2(){}
15     // 私有 單例對象 將對象的管理權封閉
16     private static SingletonExample2 instance = null;
17 
18     // 靜態的工廠方法
19     public static SingletonExample2 getInstance(){
20         if(instance == null){
21             instance = new SingletonExample2();
22         }
23         return instance;
24     }
25 }

 

  ok,我們看一下這種最簡單的懶漢式存在的問題。

  在多線程環境下,當兩個線程同時訪問這個方法,同時制定到instance==null的判斷。都判斷為null,接下來同時執行new操作。這樣類的構造函數被執行了兩次。一旦構造函數中涉及到某些資源的處理,那么就會發生錯誤。所以說最簡式是線程不安全的。

 

2.2、懶漢式 synchronized版

 1 public class SingletonExample2 {
 2     // 私有構造方法,禁止外部直接創建實例
 3     private SingletonExample2(){}
 4     // 私有 單例對象 將對象的管理權封閉
 5     private static SingletonExample2 instance = null;
 6 
 7     // 靜態的工廠方法
 8     public synchronized static SingletonExample2 getInstance(){
 9         if(instance == null){
10             instance = new SingletonExample2();
11         }
12         return instance;
13     }
14 }

 

  如上代碼所示,我們采用synchronized修飾getInstance()方法。有效的解決了線程安全性的問題,但是,,,但是synchronized直接鎖死了getInstance()方法,意味着在多線程環境下getInstance()方法的並發效率會大大降低,性能損耗十分嚴重,因為getInstance()方法已經成為了一個同步方法。

 

2.3、懶漢式的雙重檢測機制

 1 public class SingletonExample2 {
 2     // 私有構造方法,禁止外部直接創建實例
 3     private SingletonExample2(){}
 4     // 私有 單例對象 將對象的管理權封閉
 5     private static SingletonExample2 instance = null;
 6 
 7     // 靜態的工廠方法
 8     public static SingletonExample2 getInstance(){
 9         if(instance == null){// 雙重監測機制
10             synchronized(SingletonExample2.class){// 同步鎖
11                 if(instance == null){
12                     instance = new SingletonExample2();
13                 }
14             }
15         }
16         return instance;
17     }
18 }

 

  雙重檢測機制保證了 synchronized 代碼塊中代碼的線程安全性,另外當已經初始化完成了單例后調用 getInstance()方法,不會再次進入synchronized 代碼塊中,貌似是滿足了線程安全性和性能的提升。但是,,,真的是線程安全的嗎?NO,因為 instance = new SingletonExample2(); 該行代碼會發生指令重排。

  在上述代碼中,執行new操作的時候,CPU一共進行了三次指令 

1,memory = allocate() 分配對象的內存空間 
2,ctorInstance() 初始化對象 
3,instance = memory 設置instance指向剛分配的內存

  在程序運行過程中,CPU在不違背 happens-before 八大原則的前提下,為提高運算速度會做出違背代碼原有順序的優化。我們稱之為亂序執行優化或者說是指令重排。那么上面知識點中的三步指令極有可能被優化為1,3,2的順序。當我們有兩個線程A與B,A線程遵從132的順序,經過了兩此instance的空值判斷后,執行了new操作,並且cpu在某一瞬間剛結束指令3,並且還沒有執行指令2。而在此時線程B恰巧在進行第一次的instance空值判斷,由於線程A執行完3指令,為instance分配了內存,線程B判斷instance不為空,直接執行return,返回了instance,這樣就出現了錯誤。

 

2.4、懶漢式終極版:雙重檢測機制+volatile禁止指令重排

 1 package com.shf.mode.singleton;
 2 
 3 /**
 4  * 描述:懶漢式
 5  * 單例實例在第一次調用的時候創建
 6  * 典型的以時間換取空間,第一次調用 getInstance()才會實例化單例。
 7  *
 8  * @Author shf
 9  * @Date 2019/7/15 16:12
10  * @Version V1.0
11  **/
12 public class SingletonExample2 {
13     // 私有構造方法,禁止外部直接創建實例
14     private SingletonExample2(){}
15     // 1、memory = allocate() 分配對象的內存空間
16     // 2、ctorInstance() 初始化對象
17     // 3、instance = memory 設置instance指向剛分配的內存
18 
19     // 單例對象 volatile + 雙重檢測機制 -> 禁止指令重排
20     // 私有 單例對象 將對象的管理權封閉
21     private volatile static SingletonExample2 instance = null;
22 
23     // 靜態的工廠方法
24     public static SingletonExample2 getInstance(){
25         if(instance == null){// 雙重監測機制
26             synchronized(SingletonExample2.class){// 同步鎖
27                 if(instance == null){
28                     instance = new SingletonExample2();
29                 }
30             }
31         }
32         return instance;
33     }
34 }

  關於happens-before原則和volatile禁止指令重排感興趣的可以度娘一下。或者參考一下這篇博客

  雙重檢測機制又稱雙重校驗鎖(DCL,即 double-checked locking)。面試官很中意這個玩意哦。

 

三、餓漢式

  所謂餓漢式就是不管你用不用,我都先給你創建出來。空間換時間,但是畢竟如果不用的話豈不是白白浪費了空間,如果保證這個單例一定會被調用,餓漢式也是一個不錯的選擇,而且實現的難度相對於懶漢式也簡單不少。

 1 package com.shf.mode.singleton;
 2 
 3 /**
 4  * 描述:餓漢式
 5  * 單例實例在類裝載時創建
 6  * 典型的用空間換取時間----不管用不用都先創建出來
 7  *
 8  * @Author shf
 9  * @Date 2019/7/15 15:58
10  * @Version V1.0
11  **/
12 public class SingletonExample1 {
13     // 私有構造方法,禁止外部直接創建實例
14     private SingletonExample1(){}
15     // 私有 單例對象 將對象的管理權封閉
16     private static SingletonExample1 instance = null;
17     // 靜態方法塊 完成單例的實例化
18     static {
19         instance = new SingletonExample1();
20     }
21     // public 對外開發的獲取單例的唯一渠道
22     public SingletonExample1 getInstance(){
23         return instance;
24     }
25 }

 

  

TIPS:
        靜態域和靜態代碼塊的順序不要亂了哦,靜態域與靜態塊是按照順序執行的。這是Java基礎知識的內容,就不過多解釋了。

 

 

四、枚舉式

  這種實現方式還沒有被廣泛采用,但這是實現單例模式的最佳方法。它更簡潔,自動支持序列化機制,絕對防止多次實例化。這種方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不僅能避免多線程同步問題,而且還自動支持序列化機制,防止反序列化重新創建新的對象,絕對防止多次實例化,這個是JVM保證的。不過,由於 JDK1.5 之后才加入 enum 特性,用這種方式寫不免讓人感覺生疏,在實際工作中,也很少用。

 1 package com.shf.mode.singleton;
 2 
 3 /**
 4  * 描述:
 5  *
 6  * @Author shf
 7  * @Date 2019/7/16 0:28
 8  * @Version V1.0
 9  **/
10 public class SingletonExample3 {
11     // 私有構造方法,禁止外部直接創建實例
12     private SingletonExample3() {}
13 
14     public static SingletonExample3 getInstance() {
15         return Singleton.INSTANCE.getInstance();
16     }
17 
18     private enum Singleton {
19         INSTANCE;
20         private SingletonExample3 singleton;
21 
22         Singleton() {
23             singleton = new SingletonExample3();
24         }
25 
26         public SingletonExample3 getInstance() {
27             return singleton;
28         }
29     }
30 }

 

 

  

  如有錯誤的地方還請留言指正。

  原創不易,轉載請注明原文地址:https://www.cnblogs.com/hello-shf/p/11192500.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM