單例模式(Singleton Pattern)是一種創(chuàng)建型設(shè)計(jì)模式,其主要目的是確保一個(gè)類只有一個(gè)實(shí)例,并提供對該實(shí)例的唯一訪問點(diǎn)。
(資料圖)
優(yōu)缺點(diǎn)優(yōu)點(diǎn)
:
提供了對唯一實(shí)例的受控訪問。
由于在系統(tǒng)內(nèi)存中只存在一個(gè)對象,因此可以節(jié)約系統(tǒng)資源。
缺點(diǎn)
:
單例類的擴(kuò)展有很大的困難。
單例類的職責(zé)過重,在一定程度上違背了“單一職責(zé)原則”。
對象生命周期。 單例模式?jīng)]有提出對象的銷毀,在提供內(nèi)存的管理的開發(fā)語言中,只有單例模式對象自己才能將對象實(shí)例銷毀,因?yàn)橹挥兴鼡碛袑?shí)例的引用。 在各種開發(fā)語言中,比如C++,其他類可以銷毀對象實(shí)例,但是這么做將導(dǎo)致單例類內(nèi)部的指針指向不明。
單例模式的使用餓漢模式靜態(tài)成員變量/** * @author Physicx * @date 2023/5/12 下午10:13 * @desc 單例 * Created with IntelliJ IDEA */public class Singleton { //初始化實(shí)例對象 private static final Singleton instance = new Singleton(); //私有化構(gòu)造方法 private Singleton() { } //提供獲取實(shí)例對象方法 public static Singleton getInstance() { return instance; }}
靜態(tài)代碼塊/** * @author Physicx * @date 2023/5/12 下午10:13 * @desc 單例 * Created with IntelliJ IDEA */public class Singleton { //實(shí)例對象 private static final Singleton instance; static { instance = new Singleton(); } //私有化構(gòu)造方法 private Singleton() { } //提供獲取實(shí)例對象方法 public static Singleton getInstance() { return instance; }}
餓漢式單例的寫法適用于單例對象較少的情況,這樣寫可以保證絕對的線程安全,執(zhí)行效率比較高。但是缺點(diǎn)也很明顯,餓漢式會(huì)在類加載的時(shí)候就將所有單例對象實(shí)例化,這樣系統(tǒng)中如果有大量的餓漢式單例對象的存在,系統(tǒng)初始化的時(shí)候會(huì)造成大量的內(nèi)存浪費(fèi),換句話說就是不管對象用不用,對象都已存在,占用內(nèi)存。
懶漢模式public class Singleton { //實(shí)例對象 private static Singleton instance; //私有化構(gòu)造方法 private Singleton() { } //提供獲取實(shí)例對象方法(線程安全) public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; }}
線程安全的一種懶漢式寫法,在類第一次使用的時(shí)候初始化,獲取實(shí)例的靜態(tài)方法由synchronized修飾,所以是線程安全的。這種方法每次獲取實(shí)例對象都加鎖同步,效率較低。
雙重檢測機(jī)制(DCL)public class Singleton { //實(shí)例對象 private static volatile Singleton instance; //私有化構(gòu)造方法 private Singleton() { } //提供獲取實(shí)例對象方法 public static Singleton getInstance() { if (instance == null) { //加鎖處理 synchronized (Singleton.class) { if (instance==null) { //初始化 instance = new Singleton(); } } } return instance; }}
實(shí)例對象必須用 volatile
修飾,否則極端情況可能出現(xiàn)安全隱患。
以上初始化對象代碼被編譯后會(huì)變成以下三條指令:
分配對象的內(nèi)存空間。
初始化對象。
設(shè)置instance指向剛才分配的內(nèi)存空間。
如果按照上面的執(zhí)行順序則不加volatile沒有問題,但是CPU或編譯器為了提高效率,可能會(huì)進(jìn)行指令重排,最終順序變?yōu)椋?/p>
分配對象的內(nèi)存空間。
設(shè)置instance指向剛才分配的內(nèi)存空間。
初始化對象。
當(dāng)兩個(gè)線程同時(shí)獲取實(shí)例對象時(shí),線程A已經(jīng)將instance指向分配空間但未初始化對象,線程B此時(shí)第一次判空已不為空,于是返回instance實(shí)例,但是此時(shí)返回的實(shí)例未初始化會(huì)導(dǎo)致后續(xù)空指針異常。
DCL這種方式同樣也是類第一次使用的時(shí)候初始化,初始化代碼synchronized修飾線程安全,這種方式只會(huì)第一次實(shí)例對象才會(huì)進(jìn)行同步,因此效率高。
靜態(tài)內(nèi)部類(延遲初始化)《Java Concurrency in Practice》作者Brian Goetz在書中提到關(guān)于DCL的觀點(diǎn):促使DCL模式出現(xiàn)的驅(qū)動(dòng)力(無競爭同步的執(zhí)行速度很慢,以及JVM啟動(dòng)時(shí)很慢)已經(jīng)不復(fù)存在,因而它不是一種高效的優(yōu)化措施。延遲初始化占位類模式(靜態(tài)內(nèi)部類)能帶來同樣的優(yōu)勢,并且更容易理解。
public class Singleton { //私有化構(gòu)造方法 private Singleton(){} //靜態(tài)內(nèi)部類(被調(diào)用時(shí)加載) private static class SingletonHandle { private static final Singleton instance = new Singleton(); } //提供獲取實(shí)例對象方法 public static Singleton getInstance() { return SingletonHandle.instance; }}
利用靜態(tài)內(nèi)部類被調(diào)用時(shí)才加載的特性,通過靜態(tài)初始化初始Singleton對象,由于JVM將在初始化期間獲得一個(gè)鎖,并且每個(gè)線程都至少獲取一次這個(gè)鎖以確保這個(gè)類已經(jīng)加載,因此在靜態(tài)初始化期間,內(nèi)存寫入操作將自動(dòng)對所有線程可見。因此無論是在被構(gòu)造期間還是被引用時(shí),靜態(tài)初始化的對象都不需要顯式的同步。
線程安全,效率高,使用的時(shí)候才會(huì)初始化不浪費(fèi)內(nèi)存。
枚舉實(shí)現(xiàn)方式《Java Concurrency in Practice》作者Brian Goetz 推薦這種單例實(shí)現(xiàn)方式。
除了以上幾種常見的實(shí)現(xiàn)方式之外,Google 首席 Java 架構(gòu)師、《Effective Java》一書作者、Java集合框架的開創(chuàng)者Joshua Bloch在Effective Java一書中提到:單元素的枚舉類型已經(jīng)成為實(shí)現(xiàn)Singleton的最佳方法。
在這種實(shí)現(xiàn)方式中,既可以避免多線程同步問題;還可以防止通過反射和反序列化來重新創(chuàng)建新的對象。
public class Singleton { //私有化構(gòu)造方法 private Singleton() {} enum SingletonEnum { SINGLETON; private final Singleton instance; SingletonEnum() { instance = new Singleton(); } //提供獲取實(shí)例對象方法 public Singleton getInstance() { return instance; } }}
調(diào)用方式如下:
public static void main(String[] args) { Singleton instance1 = Singleton.SingletonEnum.SINGLETON.getInstance(); Singleton instance2 = Singleton.SingletonEnum.SINGLETON.getInstance(); System.out.println(instance2 == instance1); }
普通的單例模式是可以通過反射和序列化/反序列化來破解的,jvm虛擬機(jī)會(huì)保證枚舉類型不能被反射并且構(gòu)造函數(shù)只被執(zhí)行一次,而Enum由于自身的特性問題,是無法破解的。當(dāng)然,由于這種情況基本不會(huì)出現(xiàn),因此我們在使用單例模式的時(shí)候也比較少考慮這個(gè)問題。
總結(jié)實(shí)現(xiàn)方式 | 優(yōu)點(diǎn) | 缺點(diǎn) |
---|---|---|
餓漢模式 | 線程安全,效率高 | 非懶加載 |
懶漢模式 | 線程安全,懶加載 | 效率低 |
雙重檢測機(jī)制 | 線程安全,懶加載,效率高 | |
靜態(tài)內(nèi)部類 | 線程安全,懶加載,效率高 | |
枚舉 | 線程安全,效率高 | 非懶加載 |
由于單例模式的枚舉實(shí)現(xiàn)代碼比較簡單,而且又可以利用枚舉的特性來解決線程安全和單一實(shí)例的問題,還可以防止反射和反序列化對單例的破壞,因此在很多書和文章中都強(qiáng)烈推薦將該方法作為單例模式的最佳實(shí)現(xiàn)方法。
參考:單例模式詳解(知乎文章)
設(shè)計(jì)模式相關(guān)其他文章:Java設(shè)計(jì)模式總結(jié)
Copyright @ 2015-2022 太平洋家電網(wǎng)版權(quán)所有 備案號: 豫ICP備2022016495號-17 聯(lián)系郵箱:93 96 74 66 9@qq.com