欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

java中單例模式講解

 更新時間:2021年08月27日 14:34:33   作者:看山  
這篇文章主要介紹了java中單例模式,本文通過簡單的案例,講解了該模式在java中的使用,以下就是詳細內(nèi)容,需要的朋友可以參考下

個人認為單例模式是設(shè)計模式中最簡單也是最常用的一種,是對有限資源合理利用的一種方式。這個模式看似簡單,但是其中蘊含了關(guān)于并發(fā)、類加載、序列化等一系列深層次的知識,如果理解不夠深,就有可能在高并發(fā)時遇到難以預期的異常,或者會造成資源浪費。

所以本文會從將目前Java領(lǐng)域最常用的幾種單例模式列出來,供大家參考。

WHAT

維基百科給出了解釋、實現(xiàn)的思路以及應該注意的地方:

單例模式,也叫單子模式,是一種常用的軟件設(shè)計模式,屬于創(chuàng)建型模式的一種。在應用這個模式時,單例對象的類必須保證只有一個實例存在。

實現(xiàn)單例模式的思路是:一個類能返回對象一個引用(永遠是同一個)和一個獲得該實例的方法(必須是靜態(tài)方法,通常使用getInstance這個名稱);當我們調(diào)用這個方法時,如果類持有的引用不為空就返回這個引用,如果類保持的引用為空就創(chuàng)建該類的實例并將實例的引用賦予該類保持的引用;同時我們還將該類的構(gòu)造函數(shù)定義為私有方法,這樣其他處的代碼就無法通過調(diào)用該類的構(gòu)造函數(shù)來實例化該類的對象,只有通過該類提供的靜態(tài)方法來得到該類的唯一實例。

單例模式在多線程的應用場合下必須小心使用。如果當唯一實例尚未創(chuàng)建時,有兩個線程同時調(diào)用創(chuàng)建方法,那么它們同時沒有檢測到唯一實例的存在,從而同時各自創(chuàng)建了一個實例,這樣就有兩個實例被構(gòu)造出來,從而違反了單例模式中實例唯一的原則。 解決這個問題的辦法是為指示類是否已經(jīng)實例化的變量提供一個互斥鎖(雖然這樣會降低效率)。

類圖是:

singleton pattern

WHY

正如定義所說,單例模式就是整個內(nèi)存模型中,只有一個實例。實例少了,內(nèi)存占用就少。同時,只有一個實例,也就只需要構(gòu)建一個對象,計算就少。對于構(gòu)造過程中需要大量計算或者占用大量資源的對象,只創(chuàng)建一次,就減少了資源占用和內(nèi)存占用。

HOW

餓漢式

餓漢式是最簡單的一種實現(xiàn),在類裝載過程中,完成實例化,避免多線程問題。

實現(xiàn)一:靜態(tài)實例參數(shù)與靜態(tài)代碼塊

public class EagerSingleton {
    private static final EagerSingleton INSTANCE = new EagerSingleton();

    private EagerSingleton() {
    }

    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
}

根據(jù)java的特性,餓漢式還可以變種寫法,有的地方稱為靜態(tài)代碼塊方式:

public class EagerSingleton {
    private static EagerSingleton INSTANCE = null;

    static {
        INSTANCE = new EagerSingleton();
    }

    private EagerSingleton() {
    }

    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
}

這兩種方式只是在寫法上的區(qū)別,優(yōu)缺點沒有區(qū)別,只是借助Java語言特性的不同寫法,所以歸為一類。

餓漢式有兩個明顯的缺點:

1.類裝載過程即完成實例化,如果整個應用生命周期內(nèi),實例沒有使用,也就是浪費資源了。

2.因為沒有辦法向構(gòu)造函數(shù)傳遞不同的參數(shù),如果需要通過個性化參數(shù)定制實例時,這種方式就不支持了。

實現(xiàn)二:靜態(tài)內(nèi)部類

針對餓漢式第一個缺點,我們可以借助靜態(tài)內(nèi)部類的方式,將對象實例化的時間延后。

public class EagerSingleton {
    private EagerSingleton() {
    }

    private static class EagerSingletonInstance {
        private static final EagerSingleton INSTANCE = new EagerSingleton();
    }

    public static EagerSingleton getInstance() {
        return EagerSingletonInstance.INSTANCE;
    }
}

但是,依然不能很好的解決第二個缺點,如果需要根據(jù)不同的參數(shù)實現(xiàn)不同的實例,可以采用下面說的懶漢式實現(xiàn)。

懶漢式

懶漢式比餓漢式的一個優(yōu)點,就是能夠在使用的時候再進行實例化。但是,餡餅總是要伴隨著陷阱,懶漢式寫法有更多的坑,一不小心就摔著了。

錯誤一:單線程實現(xiàn)

public class LazySingleton {
    private static LazySingleton INSTANCE = null;

    private LazySingleton() {
    }

    public static LazySingleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new LazySingleton();
        }
        return INSTANCE;
    }
}

之所以定義為單線程實現(xiàn),是因為INSTANCE == null這個判斷,一個線程通過這個判斷,開始進行對象實例化,但是還沒有實例化完成,另一個線程又來了,這個時候,對象還沒有實例化,就也會開始進行實例化,造成不必要的浪費。

錯誤二:同步方法

public class LazySingleton {
    private static LazySingleton INSTANCE = null;

    private LazySingleton() {
    }

    public static synchronized LazySingleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new LazySingleton();
        }
        return INSTANCE;
    }
}

這種方式解決了多線程的問題,但是也引入了新的性能問題:太慢。synchronized把整個方法包起來,也就是每個線程進入的時候,都需要等待其他線程結(jié)束調(diào)用,才能拿到實例,在性能敏感的場景,是比較致命的。

錯誤三:同步代碼塊之單次檢查

public class LazySingleton {
    private static LazySingleton INSTANCE = null;

    private LazySingleton() {
    }

    public static LazySingleton getInstance() {
        if (INSTANCE == null) {
            synchronized (LazySingleton.class) {
                INSTANCE = new LazySingleton();
            }
        }
        return INSTANCE;
    }
}

這種寫法看似將同步代碼縮小,但也縮小了多線程保障,也犯了第一種寫法的錯誤,屬于沒有對多線程有基本了解寫出的低級錯誤代碼。

錯誤四:同步代碼塊之雙重檢查

public class LazySingleton {
    private static LazySingleton INSTANCE = null;

    private LazySingleton() {
    }

    public static LazySingleton getInstance() {
        if (INSTANCE == null) {
            synchronized (LazySingleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new LazySingleton();
                }
            }
        }
        return INSTANCE;
    }
}

這種寫法在一定程度上屬于正確的寫法,雙重判斷可以很好的實現(xiàn)線程安全和延遲加載。如果到這里就結(jié)束,那就是謬以千里的毫厘之差。

雙重檢查和同步代碼塊都沒有問題,問題出在INSTANCE = new LazySingleton()這句話。在JVM中,為了充分利用CPU計算能力,會進行重排序優(yōu)化,INSTANCE = new LazySingleton()做了三件事:

1.為 INSTANCE 初始化??臻g

2.為 LazySingleton 分配內(nèi)存空間,實例化對象

3.INSTANCE 指向 LazySingleton 實例分配的內(nèi)存空間

因為重排序優(yōu)化的存在,真正執(zhí)行的過程中,可能會出現(xiàn)1-2-3的順序,也可能出現(xiàn)1-3-2的順序。如果是1-3-2,INSTANCE 指向了 LazySingleton 實例分配的內(nèi)存空間后,就不是null,另外一個線程進入判斷null時,就會直接返回 INSTANCE,但是這個時候 LazySingleton 實例化還沒有完成,就可能出現(xiàn)意想不到的異常。

正確:雙重檢查+阻止重排序

public class LazySingleton {
    private static volatile LazySingleton INSTANCE = null;

    private LazySingleton() {
    }

    public static LazySingleton getInstance() {
        if (INSTANCE == null) {
            synchronized (LazySingleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new LazySingleton();
                }
            }
        }
        return INSTANCE;
    }
}

這種寫法比上面那種,就差在volatile這個關(guān)鍵字。

枚舉

懶漢式和餓漢式都能夠適用于多線程并發(fā)場景,但是通過反序列化或反射可以實例化對象,這樣依然不能滿足單例模式的要求,所以可以借助枚舉實現(xiàn),枚舉可以完美避免多線程并發(fā)問題,而且可以防止反序列化和反射創(chuàng)建新對象。第一次看到這樣定義單例模式,是在《Effective Java》中,多讀經(jīng)典書還是挺好的。

public enum EnumSingleton {
    INSTANCE;

    public void method1() {
        // do something
    }

    public Object method2() {
        // do something and return something else
        return new Object();
    }
}

在開發(fā)實踐中,枚舉可以滿足絕大部分場景,而且寫法簡單,定義單例的邏輯只需要三行代碼,簡潔而不簡單,三行代碼可以保證線程安全。同時枚舉的反序列化只是通過name查找對象,不會產(chǎn)生新的對象;根據(jù)JVM規(guī)范,通過反射創(chuàng)建枚舉對象時,會拋出IllegalArgumentException異常。這樣,相當于通過語法糖防止反序列化和反射破壞單例。

場景

1.無狀態(tài)工具類:這種工具類不需要記錄狀態(tài),只保證正確的應用就行,可以通過單例模式來定義。

2.數(shù)據(jù)共享:即多個不相關(guān)的兩個線程或者進程之間實現(xiàn)通信。因為是一個實例,如果它的屬性或者變量值被修改,所有引用都是同時修改的,當然需要 volatile 來定義變量。比如網(wǎng)站的計數(shù)器。

3.日志應用:通常應用會向日志文件寫日志信息,為了實時向文件寫,通常會使用單例模式,保證有一個實例持有文件,然后進行操作。

4.數(shù)據(jù)庫連接池:數(shù)據(jù)庫連接是一種數(shù)據(jù)庫資源,使用數(shù)據(jù)庫連接池,主要是節(jié)省打開或者關(guān)閉數(shù)據(jù)庫連接所引起的效率損耗,這種效率上的損耗還是非常昂貴的,通過單例模式來維護,就可以大大降低這種損耗。

5.Web應用的配置對象:讀取文件需要消耗時間,如果讀取大文件,消耗的時間和資源更久,所以通過單例模式可以大大降低消耗。


以上就是java中單例模式講解的詳細內(nèi)容,更多關(guān)于java 單例模式的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 淺析java程序入口main()方法

    淺析java程序入口main()方法

    這篇文章主要介紹了淺析java程序入口main()方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-07-07
  • SpringCloud gateway request的body驗證或修改方式

    SpringCloud gateway request的body驗證或修改方式

    這篇文章主要介紹了SpringCloud gateway request的body驗證或修改方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • SpringBoot與Angular2的集成示例

    SpringBoot與Angular2的集成示例

    本篇文章主要介紹了SpringBoot與Angular2的集成示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-03-03
  • SpringBoot2.0如何啟用https協(xié)議

    SpringBoot2.0如何啟用https協(xié)議

    這篇文章主要介紹了SpringBoot2.0如何啟用https協(xié)議,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-06-06
  • Java版微信公眾號支付開發(fā)全過程

    Java版微信公眾號支付開發(fā)全過程

    這篇文章主要介紹了Java版微信公眾號支付開發(fā)全過程,本文通過實例相結(jié)合給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2018-07-07
  • 關(guān)于log4j日志擴展---自定義PatternLayout

    關(guān)于log4j日志擴展---自定義PatternLayout

    這篇文章主要介紹了關(guān)于log4j日志擴展---自定義PatternLayout,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • 解決IDEA項目external libraries依賴包消失的問題

    解決IDEA項目external libraries依賴包消失的問題

    有時候電腦重啟后,再打開IDEA上的項目時會出現(xiàn)external libraries目錄下的依賴包都消失了的情況,只剩下了一個JDK的包,本文給大家介紹了解決IDEA項目external libraries依賴包消失的辦法,需要的朋友可以參考下
    2024-02-02
  • 支付寶APP支付(IOS手機端+java后臺)版

    支付寶APP支付(IOS手機端+java后臺)版

    這篇文章主要為大家詳細介紹了支付寶APP支付(IOS手機端+java后臺)版,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-05-05
  • IDEA創(chuàng)建Maven項目一直顯示正在加載的問題及解決

    IDEA創(chuàng)建Maven項目一直顯示正在加載的問題及解決

    這篇文章主要介紹了IDEA創(chuàng)建Maven項目一直顯示正在加載的問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • Spring實戰(zhàn)之緩存使用key操作示例

    Spring實戰(zhàn)之緩存使用key操作示例

    這篇文章主要介紹了Spring實戰(zhàn)之緩存使用key操作,結(jié)合實例形式分析了Spring緩存使用key具體配置、屬性、領(lǐng)域模型等相關(guān)操作技巧,需要的朋友可以參考下
    2020-01-01

最新評論