日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

设计模式之单例模式:7种单例设计模式(Java实现)

發布時間:2023/12/9 java 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 设计模式之单例模式:7种单例设计模式(Java实现) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

設計模式之單例模式:7種單例設計模式(Java實現)

    • 單例模式
    • 餓漢式
    • 懶漢式
    • 懶漢式+同步方法
    • Double-Check
    • Double-Check+Volatile
    • Holder方式
    • 枚舉方式
    • 各種單例模式的對比與總結

在經歷了秋招的多場面試的毒打之后,我發現Java后端開發方向的許多面試官對設計模式都挺感興趣。最近結合著汪文君老師的《Java高并發編程詳解》書籍,對設計模式進行了一些學習,本博客主要記錄一下對于單例模式的學習心得。

單例模式

單例模式是GoF23種設計模式中最常用的設計模式之一,它在日常生活中的應用有任務管理器、回收站等,特點是只能打開一個。單例模式提供了一種在多線程情況下保證實例唯一性的解決方案。
本博客記錄七種單例模式的實現方法,分別是:(1)餓漢式;(2)懶漢式;(3)懶漢式+同步方法;(4)Double-Check;(5)Double-Check + Volatile;(6)Holder方法;(7)枚舉方式。
為了比較這七種方式,我們可以從線程安全、高性能和懶加載這三個維度去衡量。

餓漢式

餓漢式的單例模式的代碼如下:

package Chapter14.Hungry;// 餓漢式的單例模式的設計 // final不允許被繼承 public final class Singleton {// 實例變量private byte[] data = new byte[1024];// 在定義實例對象的時候直接初始化private static Singleton instance = new Singleton();// 私有構造函數,不允許外部newprivate Singleton() {}public static Singleton getInstance() {return instance;} }

其中,Singleton類是使用final修飾的,不允許被繼承。data是該類的一個實例變量,占用1KB的空間。接下來的其他方式中也會存在相似的部分。
實現單例模式的重點在于:instance對象、私有構造函數和獲取instance對象的方法。下面具體分析一下:
在餓漢式的單例模式下,如果主動使用了Singleton類,instance在類加載的初始化階段就會直接完成創建,因此該方法在多線程的情況下也能保證同步,因為不可能被實例化兩次,不存在線程不安全的情況。
如果一個類的成員屬性比較少,且占用的內存資源不多,餓漢式的性能是比較高的(因為getInstance()方法非常簡單);否則,餓漢式存在的缺點就被放大,餓漢式也不是較優的選擇了。
餓漢式的主要缺點,就是餓漢式不能提供懶加載,在使用Singleton類時就創建了成員屬性,一直占用著內存資源。我們更希望能實現懶加載的方式,創建類時可以沒有data這些實例變量,直到使用getInstance()方法時再創建。

懶漢式

懶漢式的單例模式的代碼如下:

package Chapter14.Lazy;public final class Singleton {private byte[] data = new byte[1024];private static Singleton instance = null;private Singleton() {}public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;} }

對比一下餓漢式,我們可以發現懶漢式的主要變動就是,在類加載時,instance對象為空,直到getInstance()方法被調用,判斷instance是否仍然為空,若為空才進行賦值。這種區別也體現在“餓漢式”與“懶漢式”的名稱的區別上,“餓漢”看到東西就會去吃(類加載時就賦值instance),“懶漢”總是把事情拖到最后一刻(調用了getInstance()方法才去賦值instance)。
懶漢式的效能還是比較高的,也可以實現懶加載。但是上述懶漢式的代碼并不能保證線程同步。問題出在getInstance()方法中:原instance為null,如果兩個線程A和B,A調用getInstance(),執行instance = new Singletion()需要一定時間;在這段時間中,B也調用了getInstance(),也要去執行instance = new Singletion(),這樣instance就被實例化了兩次,線程不同步了。

懶漢式+同步方法

為了解決上述懶漢式設計模式中存在的問題,可以強制進行同步,即使用synchronized關鍵字去修飾getInstance()方法。代碼如下:

package Chapter14.LazySynchronized;public final class Singleton {private byte[] data = new byte[1024];private static Singleton instance = null;private Singleton() {}public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;} }

實際上就是在getInstance()方法前添加了synchronized關鍵字進行修飾。如此,同步的懶漢式可以保證線程安全,也可以實現懶加載。但是synchronized關鍵字天生的排他性導致了getInstance()方法在同一時刻只能被一個線程所訪問,性能低下。

Double-Check

Double-Check在懶漢式的基礎上,提供了一種高效的數據同步策略——首次初始化時加鎖,之后則允許多個線程同步調用getInstance()方法獲得實例。具體代碼如下:

package Chapter14.DoubleCheck;import java.net.Socket; import java.sql.Connection;public final class Singleton {private byte[] data = new byte[1024];private static Singleton instance = null;// conn和socket模擬其他需要初始化的實例變量Connection conn;Socket socket;private Singleton() {// 此處需要實例化conn和socket等實例變量}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}}

在這里我們設置了conn和socket實例變量,用來模擬單例模式中一些需要進行初始化設置的實例變量,比如Connection conn需要與數據庫進行連接,Socket socket需要建立一個TCP連接等。接下來將會提到Double-Check的問題,就會和這些實例變量有關。
分析一下Double-Check如何實現線程安全:當兩個線程A和B同時發現instance == null時,只有線程A有資格進入同步代碼塊,完成對instance的實例化;等到A釋放同步資源后,線程B發現instance != null,就只需要去獲取instance即可。
因此Double-Check方式可以保證線程安全,也很高效,同時也支持懶加載。但是Double-Check有個致命的問題,那就是可能會引發空指針異常。下面我們來分析一下為什么會發生異常。
在Singleton構造函數中,我們需要實例化instance本身,同時還要實例化conn和socket這兩個資源。假設線程A第一個調用getInstance()方法,此時instance == null,因此A獲取到同步資源,對instance進行加載。由于指令會被重排序,在Singleton的構造函數中,有可能instance最先被實例化,而conn和socket等資源后被實例化。在conn和socket被實例化的過程中,又有一個線程B調用了getInstance()方法,它發現instance != null,則獲取到了instance,然后又去使用conn或socket,就會產生空指針異常。

Double-Check+Volatile

既然我們分析到了,產生異常的原因在于指令可能會被重排序,導致instance被實例化早于其他實例資源。因此我們可以用volatile關鍵字去修飾instance,并嚴格管理Singleton構造函數中各個資源與instance的順序。具體代碼如下:

package Chapter14.DoubleCheckVolatile;import java.net.Socket; import java.sql.Connection;public final class Singleton {private byte[] data = new byte[1024];// 加volatile關鍵字保證有序性,防止指令重排序private volatile static Singleton instance = null;Connection conn;Socket socket;private Singleton() {// 先實例化conn和socket等實例變量// 最后實例化instance}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;} }

由此,我們實現了一種滿足線程安全、高性能、懶加載的單例模式。不過這種方式用得還是比較少,更常見的是下面兩種單例模式。

Holder方式

Holder方式的單例模式的代碼如下:

package Chapter14.Holder;public final class Singleton {private byte[] data = new byte[1024];private Singleton() {}// 在靜態內部類中持有Singleton的實例,并且可被直接初始化private static class Holder {private static Singleton instance = new Singleton();}// 調用getInstance()方法,實際上是獲得Holder的instance靜態屬性public static Singleton getInstance() {return Holder.instance;} }

Holder方式完全借助了類加載的特性。在Singleton類中并沒有instance的靜態成員變量,只有在其靜態內部類Holder中有。因此,在Singleton類初始化時,并不會創建Singleton的實例。只有在getInstance()方法第一次被調用時,Singleton的實例instance才會被創建,由此實現了懶加載。另外,這個創建過程是在Java編譯時期收集至<clinit>()方法(JVM的一個內部方法,在類加載的初始化階段起作用)中的。這個方法是同步方法,保證內存的可見性、JVM指令的原子性和有序性。
因此Holder方式可以保證線程安全、高性能和懶加載,它也是單例模式中最好的設計之一,使用非常廣泛。

枚舉方式

枚舉方式是《Effective Java》作者力推的方式,在很多優秀開源代碼中經常可以看到枚舉方式的例子。枚舉方式的示例代碼如下:

package Chapter14.Enum;public enum Singleton {INSTANCE;private byte[] data = new byte[1024];Singleton() {System.out.println("INSTANCE will br initialized immediately");}public static void method() {// 調用該方法將會主動使用Singleton, INSTANCE將會被初始化}public static Singleton getInstance() {return INSTANCE;}}

枚舉方式不能被繼承,只能實例化一次,同樣也是線程安全,且高效的。但是枚舉方式不能懶加載,第一次調用Singleton枚舉時,instance就會被實例化。
可以根據Holder方式,對枚舉方式進行改造,改造后的代碼如下:

package Chapter14.EnumLazyLoad;public class Singleton {private byte[] data = new byte[1024];private Singleton() {}private enum EnumHolder {INSTANCE;private Singleton instance;EnumHolder() {this.instance = new Singleton();}private Singleton getSingleton() {return instance;}}public static Singleton getInstance() {return EnumHolder.INSTANCE.getSingleton();} }

各種單例模式的對比與總結

下表展示了各種單例模式在線程安全、高性能、懶加載維度的一些比較:

線程安全高性能懶加載其他
餓漢式×
懶漢式×
懶漢式+同步方法×
Double-Check會引發空指針異常
Double-Check+Volatile
Holder方式
枚舉方式×可以添加Holder方式實現懶加載
枚舉方式+Holder方式

最后,《Java高并發編程詳解》的作者汪文君老師指出,在實際開發中,Holder方式和枚舉方式是最常見的設計單例的方式。
我們可以多多學習、應用,最終完全掌握單例模式,并進一步學習其他的設計模式。在開發中,各種設計模式還是很重要的。

總結

以上是生活随笔為你收集整理的设计模式之单例模式:7种单例设计模式(Java实现)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。