一、介紹
單例模式(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