JAVA - 單例設計模式
- 一. 簡介
- 二. 單例模式的原則
- 三. 單例模式的實現
- 1.1 餓漢式
- 1.1.1 靜態變量初始化方式
- 1.1.2 靜態代碼塊初始化方式
- 1.1.3 枚舉方式
- 1.2 懶漢式
- 1.2.1 懶加載初始化方法 (線程不安全)
- 1.2.2 懶加載初始化方法 (線程安全)
- 1.2.3 雙重檢查鎖
- 1.2.4 靜態內部類方式
前言
這是我在這個網站整理的筆記,有錯誤的地方請指出,關注我,接下來還會持續更新。 作者:神的孩子都在歌唱
一. 簡介
單例模式(Singleton Pattern的設計模式屬于創建型模式,它提供了一種創建對象的最佳方式。
這種模式涉及到一個單一的類,該類負責創建自己的對象,同時確保只有單個對象被創建。這個類提供了一種訪問其唯一的對象的方式,可以直接訪問,不需要實例化該類的對象。
二. 單例模式的原則
- 單例模式限制類的實例化,并確保Java虛擬機中只存在該類的一個實例。
- 單例類必須提供一個全局訪問點來獲取該類的實例。
- 單例模式用于日志記錄、驅動對象、緩存和線程池。
- 單例設計模式還用于其他設計模式,如抽象工廠等。
- 單例設計模式也用在核心 Java 類中(例如,
java.lang.Runtime
、java.awt.Desktop
)。
三. 單例模式的實現
單例設計模式分類兩種:
? 餓漢式:類加載就會導致該單實例對象被創建
? 懶漢式:類加載不會導致該單實例對象被創建,而是首次使用該對象時才會創建
? 簡單理解:在自己身體里面創建自己,給外部調用
1.1 餓漢式
1.1.1 靜態變量初始化方式
在系統初始化時候,單例類的實例是在類加載時創建的。靜態變量方式初始化的缺點是,即使客戶端應用程序可能沒有使用該方法,也會創建該方法。這是靜態初始化單例類的實現:
/*** @author: 那就叫小智吧* @date: 2022/3/3 15:41* @Description: 靜態變量創建類對象* 該方式在成員位置聲明Singleton類型的靜態變量,并創建Singleton類的對象instance。instance對象是隨著類的加載而創建的。如果該對象足夠大的話,而一直沒有使用就會造成內存的浪費。*/
public class Singleton1 {// 私有構造方法private Singleton1(){System.out.println("通過靜態變量創建類對象");}// 在成員位置創建該類的對象private static Singleton1 instance= new Singleton1();// 對外提供靜態方法獲取該對象public static Singleton1 getInstance(){return instance;}
}
如果您的單例類沒有使用大量資源,則可以使用這種方法。但在大多數情況下,單例類是為文件系統、數據庫連接等資源創建的。除非客戶端調用該
getInstance
方法,否則我們應該避免實例化。此外,此方法不提供任何異常處理選項。
1.1.2 靜態代碼塊初始化方式
靜態代碼塊方式實現與靜態變量初始化方式類似,不同之處在于類的實例是在提供異常處理選項的靜態塊中創建的。
/*** @author: 那就叫小智吧* @date: 2022/3/3 15:49* @Description: 在靜態代碼塊中創建該類對象* 該方式在成員位置聲明Singleton類型的靜態變量,而對象的創建是在靜態代碼塊中,也是對著類的加載而創建。當然該方式也存在內存浪費問題。*/
public class Singleton2 {// 私有構造方法private Singleton2(){System.out.println("在靜態代碼塊中創建該類對象");};// 在成員位置聲明靜態變量private static Singleton2 instance;static {try {instance = new Singleton2();} catch (Exception e) {throw new RuntimeException("創建單例實例時發生異常");}}// 對外提供靜態方法獲取該對象public static Singleton2 getInstance(){return instance;}}
靜態代碼塊方式實現與靜態變量初始化方式 都會在使用實例之前創建實例,但這不是最佳實踐。
1.1.3 枚舉方式
/*** @author: 那就叫小智吧* @date: 2022/3/3 16:36* @Description: 惡漢式* 枚舉類實現單例模式是極力推薦的單例實現模式,因為枚舉類型是線程安全的,并且只會裝載一次,設計者充分的利用了枚舉的這個特性來實現單例模式,枚舉的寫法非常簡單,而且枚舉類型是所用單例實現中唯一一種不會被破壞的單例實現模式。*/
public enum Singleton {INSTANCE;
}
使用
enum
Java 來實現單例設計模式,以確保任何enum
值在 Java 程序中僅實例化一次。由于Java 枚舉值是全局可訪問的,因此單例也是如此。缺點是enum
類型有些不靈活(例如,它不允許延遲初始化)。
1.2 懶漢式
1.2.1 懶加載初始化方法 (線程不安全)
/*** @author: 那就叫小智吧* @date: 2022/3/3 15:59* @Description: 懶漢式 :線程不安全* 從下面面代碼我們可以看出該方式在成員位置聲明Singleton類型的靜態變量,并沒有進行對象的賦值操作,* 那么什么時候賦值的呢?當調用getInstance()方法獲取Singleton類的對象的時候才創建Singleton類的對象,這樣就實現了懶加載的效果。但是,如果是多線程環境,會出現線程安全問題。*/
public class Singleton1 {// 構造私有方法private Singleton1(){System.out.println("懶漢式:線程不安全");}// 在成員位置聲明靜態變量private static Singleton1 instance;// 對外提供靜態方法獲取改對象public static Singleton1 getInstance(){if (instance == null){instance = new Singleton1();}return instance;}
}
這種實現在單線程環境中工作得很好,但是當涉及到多線程系統時,如果多個線程
if
同時處于該條件內,則可能會導致問題。它將破壞單例模式,并且兩個線程將獲得單例類的不同實例。
1.2.2 懶加載初始化方法 (線程安全)
創建線程安全單例類的一種簡單方法是使全局訪問方法同步,以便一次只有一個線程可以執行該方法。以下是此方法的一般實現:
/*** @author: 那就叫小智吧* @date: 2022/3/3 16:04* @Description: 懶漢式 : 線程安全* 該方式也實現了懶加載效果,同時又解決了線程安全問題。但是在getInstance()方法上添加了synchronized關鍵字,導致該方法的執行效果特別低。從上面代碼我們可以看出,其實就是在初始化instance的時候才會出現線程安全問題,一旦初始化完成就不存在了。*/
public class Singleton2 {// 私有構造方法private Singleton2(){};// 在成員位置聲明靜態變量private static Singleton2 instance;// 對外提供靜態方法獲取對象public static synchronized Singleton2 getInstance(){if (instance != null) {instance = new Singleton2();}return instance;}
}
1.2.3 雙重檢查鎖
前面的實現,能夠正常運行并且提供了線程安全性,但是由于與同步方法相關的成本,它降低了性能,盡管我們只需要它用于可能創建單獨實例的前幾個線程。為了避免每次都產生額外的開銷,使用了雙重檢查鎖定原則。
/*** @author: 那就叫小智吧* @date: 2022/3/3 16:08* @Description: 懶漢式 :雙重檢查方式* 對于 `getInstance()` 方法來說,絕大部分的操作都是讀操作,讀操作是線程安全的,所以我們沒必讓每個線程必須持有鎖才能調用該方法,我們需要調整加鎖的時機。由此也產生了一種新的實現模式:雙重檢查鎖模式*/
public class Singleton3 {// 私有構造方法private Singleton3() {System.out.println("懶漢式 : 雙重檢查方式");}// 在成員位置聲明靜態變量private static Singleton3 instance;// 對外提供靜態方法獲取該對象public static Singleton3 getInstance() {// 第一次判斷,如果instance不為null,不進入槍鎖階段,直接返回實例if (instance == null) {synchronized (Singleton3.class) {// 搶到鎖之后再次判斷是否為nullif (instance == null) {instance = new Singleton3();}}}return instance;}
}
檢查鎖模式帶來空指針異常的問題
/*** @author: 那就叫小智吧* @date: 2022/3/3 16:25* @Description: 懶漢式: 雙重檢查方式* 雙重檢查鎖模式是一種非常好的單例實現模式,解決了單例、性能、線程安全問題,上面的雙重檢測鎖模式看上去完美無缺,其實是存在問題,在多線程的情況下,可能會出現空指針問題,出現問題的原因是JVM在實例化對象的時候會進行優化和指令重排序操作。** 要解決雙重檢查鎖模式帶來空指針異常的問題,只需要使用 `volatile` 關鍵字, `volatile` 關鍵字可以保證可見性和有序性。*/
public class Singleton4 {// 私有構造方法private Singleton4() {}private static volatile Singleton4 instance;// 對外提供靜態方法獲取該對象public static Singleton4 getInstance() {//第一次判斷,如果instance不為null,不進入搶鎖階段,直接返回實際if(instance == null) {synchronized (Singleton4.class) {//搶到鎖之后再次判斷是否為空if(instance == null) {instance = new Singleton4();}}}return instance;}}
添加 volatile
關鍵字之后的雙重檢查鎖模式是一種比較好的單例實現模式,能夠保證在多線程的情況下線程安全也不會有性能問題。
1.2.4 靜態內部類方式
/*** @author: 那就叫小智吧* @date: 2022/3/3 16:29* @Description: 懶漢式 : 靜態內部類方式* 靜態內部類單例模式中實例由內部類創建,由于 JVM 在加載外部類的過程中, 是不會加載靜態內部類的, 只有內部類的屬性/方法被調用時才會被加載,并初始化其靜態屬性。靜態屬性由于被 `static` 修飾,保證只被實例化一次,并且嚴格保證實例化順序。*/
public class Singleton5 {// 私有構造方法private Singleton5() {}// 創建靜態內部類private static class SingletonHolder {private static final Singleton5 instance = new Singleton5();}//對外提供靜態方法獲取該對象public static Singleton5 getInstance() {return SingletonHolder.instance;}}
/**
第一次加載Singleton類時不會去初始化INSTANCE,只有第一次調用getInstance,虛擬機加載SingletonHolder并初始化INSTANCE,這樣不僅能確保線程安全,也能保證 Singleton 類的唯一性。*/
文章參考地址
作者:神的孩子都在歌唱
本人博客:https://blog.csdn.net/weixin_46654114
轉載說明:務必注明來源,附帶本人博客連接。