深入理解單例模式

一撼嗓、概述

單例模式是面試中經(jīng)常會(huì)被問(wèn)到的一個(gè)問(wèn)題狂塘,網(wǎng)上有大量的文章介紹單例模式的實(shí)現(xiàn)夜焦,本文也是參考那些優(yōu)秀的文章來(lái)做一個(gè)總結(jié)懒震,通過(guò)自己在學(xué)習(xí)過(guò)程中的理解進(jìn)行記錄罩息,并補(bǔ)充完善一些內(nèi)容,一方面鞏固自己所學(xué)的內(nèi)容个扰,另一方面希望能對(duì)其他同學(xué)提供一些幫助瓷炮。

本文主要從以下幾個(gè)方面介紹單例模式:

  1. 單例模式是什么
  2. 單例模式的使用場(chǎng)景
  3. 單例模式的優(yōu)缺點(diǎn)
  4. 單例模式的實(shí)現(xiàn)(重點(diǎn))
  5. 總結(jié)

二、單例模式是什么

23 種設(shè)計(jì)模式可以分為三大類:創(chuàng)建型模式递宅、行為型模式娘香、結(jié)構(gòu)型模式。單例模式屬于創(chuàng)建型模式的一種办龄,單例模式是最簡(jiǎn)單的設(shè)計(jì)模式之一:單例模式只涉及一個(gè)類烘绽,確保在系統(tǒng)中一個(gè)類只有一個(gè)實(shí)例,并提供一個(gè)全局訪問(wèn)入口俐填。許多時(shí)候整個(gè)系統(tǒng)只需要擁有一個(gè)全局對(duì)象安接,這樣有利于我們協(xié)調(diào)系統(tǒng)整體的行為。

三玷禽、單例模式的使用場(chǎng)景

1赫段、 日志類

日志類通常作為單例實(shí)現(xiàn),并在所有應(yīng)用程序組件中提供全局日志訪問(wèn)點(diǎn)矢赁,而無(wú)需在每次執(zhí)行日志操作時(shí)創(chuàng)建對(duì)象。

2贬丛、 配置類

將配置類設(shè)計(jì)為單例實(shí)現(xiàn)撩银,比如在某個(gè)服務(wù)器程序中,該服務(wù)器的配置信息存放在一個(gè)文件中豺憔,這些配置數(shù)據(jù)由一個(gè)單例對(duì)象統(tǒng)一讀取额获,然后服務(wù)進(jìn)程中的其他對(duì)象再通過(guò)這個(gè)單例對(duì)象獲取這些配置信息够庙,這種方式簡(jiǎn)化了在復(fù)雜環(huán)境下的配置管理。

3抄邀、工廠類

假設(shè)我們?cè)O(shè)計(jì)了一個(gè)帶有工廠的應(yīng)用程序耘眨,以在多線程環(huán)境中生成帶有 ID 的新對(duì)象(Acount、Customer境肾、Site剔难、Address 對(duì)象)。如果工廠在 2 個(gè)不同的線程中被實(shí)例化兩次奥喻,那么 2 個(gè)不同的對(duì)象可能有 2 個(gè)重疊的 id偶宫。如果我們將工廠實(shí)現(xiàn)為單例,我們就可以避免這個(gè)問(wèn)題环鲤,結(jié)合抽象工廠或工廠方法和單例設(shè)計(jì)模式是一種常見(jiàn)的做法纯趋。

4、以共享模式訪問(wèn)資源的類

比如網(wǎng)站的計(jì)數(shù)器冷离,一般也是采用單例模式實(shí)現(xiàn)吵冒,如果你存在多個(gè)計(jì)數(shù)器,每一個(gè)用戶的訪問(wèn)都刷新計(jì)數(shù)器的值西剥,這樣的話你的實(shí)計(jì)數(shù)的值是難以同步的桦锄。但是如果采用單例模式實(shí)現(xiàn)就不會(huì)存在這樣的問(wèn)題,而且還可以避免線程安全問(wèn)題蔫耽。

5结耀、在Spring中創(chuàng)建的Bean實(shí)例默認(rèn)都是單例模式存在的。

適用場(chǎng)景:

  • 需要生成唯一序列的環(huán)境
  • 需要頻繁實(shí)例化然后銷(xiāo)毀的對(duì)象匙铡。
  • 創(chuàng)建對(duì)象時(shí)耗時(shí)過(guò)多或者耗資源過(guò)多图甜,但又經(jīng)常用到的對(duì)象。
  • 方便資源相互通信的環(huán)境

四鳖眼、單例模式的優(yōu)缺點(diǎn)

優(yōu)點(diǎn):

  • 在內(nèi)存中只有一個(gè)對(duì)象黑毅,節(jié)省內(nèi)存空間;

  • 避免頻繁的創(chuàng)建銷(xiāo)毀對(duì)象钦讳,減輕 GC 工作矿瘦,同時(shí)可以提高性能;

  • 避免對(duì)共享資源的多重占用愿卒,簡(jiǎn)化訪問(wèn)缚去;

  • 為整個(gè)系統(tǒng)提供一個(gè)全局訪問(wèn)點(diǎn)。

缺點(diǎn):

  • 不適用于變化頻繁的對(duì)象琼开;

  • 濫用單例將帶來(lái)一些負(fù)面問(wèn)題易结,如為了節(jié)省資源將數(shù)據(jù)庫(kù)連接池對(duì)象設(shè)計(jì)為的單例類,可能會(huì)導(dǎo)致共享連接池對(duì)象的程序過(guò)多而出現(xiàn)連接池溢出;

  • 如果實(shí)例化的對(duì)象長(zhǎng)時(shí)間不被利用搞动,系統(tǒng)會(huì)認(rèn)為該對(duì)象是垃圾而被回收躏精,這可能會(huì)導(dǎo)致對(duì)象狀態(tài)的丟失;

五鹦肿、單例模式的實(shí)現(xiàn)(重點(diǎn))

實(shí)現(xiàn)單例模式的步驟如下:

  1. 私有化構(gòu)造方法矗烛,避免外部類通過(guò) new 創(chuàng)建對(duì)象
  2. 定義一個(gè)私有的靜態(tài)變量持有自己的類型
  3. 對(duì)外提供一個(gè)靜態(tài)的公共方法來(lái)獲取實(shí)例
  4. 如果實(shí)現(xiàn)了序列化接口需要保證反序列化不會(huì)重新創(chuàng)建對(duì)象

1、餓漢式箩溃,線程安全

餓漢式單例模式瞭吃,顧名思義,類一加載就創(chuàng)建對(duì)象碾篡,這種方式比較常用虱而,但容易產(chǎn)生垃圾對(duì)象,浪費(fèi)內(nèi)存空間开泽。

優(yōu)點(diǎn):線程安全牡拇,沒(méi)有加鎖,執(zhí)行效率較高
缺點(diǎn):不是懶加載穆律,類加載時(shí)就初始化惠呼,浪費(fèi)內(nèi)存空間

懶加載 (lazy loading):使用的時(shí)候再創(chuàng)建對(duì)象

餓漢式單例是如何保證線程安全的呢?它是基于類加載機(jī)制避免了多線程的同步問(wèn)題峦耘,但是如果類被不同的類加載器加載就會(huì)創(chuàng)建不同的實(shí)例剔蹋。

代碼實(shí)現(xiàn),以及使用反射破壞單例:

/**
 * 餓漢式單例測(cè)試
 *
 * @className: Singleton
 * @date: 2021/6/7 14:32
 */
public class Singleton  {
    // 1辅髓、私有化構(gòu)造方法
    private Singleton(){}
    // 2泣崩、定義一個(gè)靜態(tài)變量指向自己類型
    private final static Singleton instance = new Singleton();
    // 3、對(duì)外提供一個(gè)公共的方法獲取實(shí)例
    public static Singleton getInstance() {
        return instance;
    }

}

使用反射破壞單例洛口,代碼如下:


public class Test {

    public static void main(String[] args) throws Exception{
        // 使用反射破壞單例
        // 獲取空參構(gòu)造方法
        Constructor<Singleton> declaredConstructor = Singleton.class.getDeclaredConstructor(null);
        // 設(shè)置強(qiáng)制訪問(wèn)
        declaredConstructor.setAccessible(true);
        // 創(chuàng)建實(shí)例
        Singleton singleton = declaredConstructor.newInstance();
        System.out.println("反射創(chuàng)建的實(shí)例" + singleton);
        System.out.println("正常創(chuàng)建的實(shí)例" + Singleton.getInstance());
        System.out.println("正常創(chuàng)建的實(shí)例" + Singleton.getInstance());
    }
}

輸出結(jié)果如下:

反射創(chuàng)建的實(shí)例com.example.spring.demo.single.Singleton@6267c3bb
正常創(chuàng)建的實(shí)例com.example.spring.demo.single.Singleton@533ddba
正常創(chuàng)建的實(shí)例com.example.spring.demo.single.Singleton@533ddba

2矫付、懶漢式,線程不安全

這種方式在單線程下使用沒(méi)有問(wèn)題第焰,對(duì)于多線程是無(wú)法保證單例的买优,這里列出來(lái)是為了和后面使用鎖保證線程安全的單例做對(duì)比。

優(yōu)點(diǎn):懶加載

缺點(diǎn):線程不安全

代碼實(shí)現(xiàn)如下:


/**
 * 懶漢式單例挺举,線程不安全
 *
 * @className: Singleton
 * @date: 2021/6/7 14:32
 */
public class Singleton  {
    // 1杀赢、私有化構(gòu)造方法
    private Singleton(){ }
    // 2、定義一個(gè)靜態(tài)變量指向自己類型
    private static Singleton instance;
    // 3湘纵、對(duì)外提供一個(gè)公共的方法獲取實(shí)例
    public static Singleton getInstance() {
        // 判斷為 null 的時(shí)候再創(chuàng)建對(duì)象
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

使用多線程破壞單例脂崔,測(cè)試代碼如下:

public class Test {

    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                System.out.println("多線程創(chuàng)建的單例:" + Singleton.getInstance());
            }).start();
        }
    }
}

輸出結(jié)果如下:

多線程創(chuàng)建的單例:com.example.spring.demo.single.Singleton@18396bd5
多線程創(chuàng)建的單例:com.example.spring.demo.single.Singleton@7f23db98
多線程創(chuàng)建的單例:com.example.spring.demo.single.Singleton@5000d44

3、懶漢式瞻佛,線程安全

懶漢式單例如何保證線程安全呢脱篙?通過(guò) synchronized 關(guān)鍵字加鎖保證線程安全娇钱,synchronized 可以添加在方法上面伤柄,也可以添加在代碼塊上面绊困,這里演示添加在方法上面,存在的問(wèn)題是每一次調(diào)用 getInstance 獲取實(shí)例時(shí)都需要加鎖和釋放鎖适刀,這樣是非常影響性能的秤朗。

優(yōu)點(diǎn):懶加載,線程安全

缺點(diǎn):效率較低

代碼實(shí)現(xiàn)如下:

/**
 * 懶漢式單例笔喉,方法上面添加 synchronized 保證線程安全
 *
 * @className: Singleton
 * @date: 2021/6/7 14:32
 */
public class Singleton  {
    // 1取视、私有化構(gòu)造方法
    private Singleton(){ }
    // 2、定義一個(gè)靜態(tài)變量指向自己類型
    private static Singleton instance;
    // 3常挚、對(duì)外提供一個(gè)公共的方法獲取實(shí)例
    public synchronized static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

4作谭、雙重檢查鎖(DCL, 即 double-checked locking)

實(shí)現(xiàn)代碼如下:


/**
 * 雙重檢查鎖(DCL奄毡, 即 double-checked locking)
 *
 * @className: Singleton
 * @date: 2021/6/7 14:32
 */
public class Singleton {
    // 1折欠、私有化構(gòu)造方法
    private Singleton() {
    }

    // 2、定義一個(gè)靜態(tài)變量指向自己類型
    private volatile static Singleton instance;

    // 3吼过、對(duì)外提供一個(gè)公共的方法獲取實(shí)例
    public synchronized static Singleton getInstance() {
        // 第一重檢查是否為 null
        if (instance == null) {
            // 使用 synchronized 加鎖
            synchronized (Singleton.class) {
                // 第二重檢查是否為 null
                if (instance == null) {
                    // new 關(guān)鍵字創(chuàng)建對(duì)象不是原子操作
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

優(yōu)點(diǎn):懶加載锐秦,線程安全,效率較高

缺點(diǎn):實(shí)現(xiàn)較復(fù)雜

這里的雙重檢查是指兩次非空判斷盗忱,鎖指的是 synchronized 加鎖酱床,為什么要進(jìn)行雙重判斷,其實(shí)很簡(jiǎn)單趟佃,第一重判斷扇谣,如果實(shí)例已經(jīng)存在,那么就不再需要進(jìn)行同步操作闲昭,而是直接返回這個(gè)實(shí)例罐寨,如果沒(méi)有創(chuàng)建,才會(huì)進(jìn)入同步塊汤纸,同步塊的目的與之前相同衩茸,目的是為了防止有多個(gè)線程同時(shí)調(diào)用時(shí),導(dǎo)致生成多個(gè)實(shí)例贮泞,有了同步塊楞慈,每次只能有一個(gè)線程調(diào)用訪問(wèn)同步塊內(nèi)容,當(dāng)?shù)谝粋€(gè)搶到鎖的調(diào)用獲取了實(shí)例之后啃擦,這個(gè)實(shí)例就會(huì)被創(chuàng)建囊蓝,之后的所有調(diào)用都不會(huì)進(jìn)入同步塊,直接在第一重判斷就返回了單例令蛉。

關(guān)于內(nèi)部的第二重空判斷的作用聚霜,當(dāng)多個(gè)線程一起到達(dá)鎖位置時(shí)狡恬,進(jìn)行鎖競(jìng)爭(zhēng),其中一個(gè)線程獲取鎖蝎宇,如果是第一次進(jìn)入則為 null弟劲,會(huì)進(jìn)行單例對(duì)象的創(chuàng)建,完成后釋放鎖姥芥,其他線程獲取鎖后就會(huì)被空判斷攔截兔乞,直接返回已創(chuàng)建的單例對(duì)象。

其中最關(guān)鍵的一個(gè)點(diǎn)就是 volatile 關(guān)鍵字的使用凉唐,關(guān)于 volatile 的詳細(xì)介紹可以直接搜索 volatile 關(guān)鍵字即可庸追,有很多寫(xiě)的非常好的文章,這里不做詳細(xì)介紹台囱,簡(jiǎn)單說(shuō)明一下淡溯,雙重檢查鎖中使用 volatile 的兩個(gè)重要特性:可見(jiàn)性、禁止指令重排序

這里為什么要使用 volatile簿训?

這是因?yàn)?new 關(guān)鍵字創(chuàng)建對(duì)象不是原子操作咱娶,創(chuàng)建一個(gè)對(duì)象會(huì)經(jīng)歷下面的步驟:

  1. 在堆內(nèi)存開(kāi)辟內(nèi)存空間
  2. 調(diào)用構(gòu)造方法,初始化對(duì)象
  3. 引用變量指向堆內(nèi)存空間

對(duì)應(yīng)字節(jié)碼指令如下:

image.png

為了提高性能煎楣,編譯器和處理器常常會(huì)對(duì)既定的代碼執(zhí)行順序進(jìn)行指令重排序豺总,從源碼到最終執(zhí)行指令會(huì)經(jīng)歷如下流程:

graph LR
A[源碼] -->B([編譯器優(yōu)化重排序])-->C([指令級(jí)并行重排序])-->D([內(nèi)存系統(tǒng)重排序])-->E[最終執(zhí)行指令序列]

所以經(jīng)過(guò)指令重排序之后,創(chuàng)建對(duì)象的執(zhí)行順序可能為 1 2 3 或者 1 3 2择懂,因此當(dāng)某個(gè)線程在亂序運(yùn)行 1 3 2 指令的時(shí)候喻喳,引用變量指向堆內(nèi)存空間,這個(gè)對(duì)象不為 null困曙,但是沒(méi)有初始化表伦,其他線程有可能這個(gè)時(shí)候進(jìn)入了 getInstance 的第一個(gè) if(instance == null) 判斷不為 nulll ,導(dǎo)致錯(cuò)誤使用了沒(méi)有初始化的非 null 實(shí)例慷丽,這樣的話就會(huì)出現(xiàn)異常蹦哼,這個(gè)就是著名的 DCL 失效問(wèn)題。

當(dāng)我們?cè)谝米兞可厦嫣砑?volatile 關(guān)鍵字以后要糊,會(huì)通過(guò)在創(chuàng)建對(duì)象指令的前后添加內(nèi)存屏障來(lái)禁止指令重排序纲熏,就可以避免這個(gè)問(wèn)題,而且對(duì) volatile 修飾的變量的修改對(duì)其他任何線程都是可見(jiàn)的锄俄。

5局劲、靜態(tài)內(nèi)部類

代碼實(shí)現(xiàn)如下:


/**
 * 靜態(tài)內(nèi)部類實(shí)現(xiàn)單例
 *
 * @className: Singleton
 * @date: 2021/6/7 14:32
 */
public class Singleton {
    // 1、私有化構(gòu)造方法
    private Singleton() {
    }
    
    // 2奶赠、對(duì)外提供獲取實(shí)例的公共方法
    public static Singleton getInstance() {
        return InnerClass.INSTANCE;
    }

    // 定義靜態(tài)內(nèi)部類
    private static class InnerClass{
        private final static Singleton INSTANCE = new Singleton();
    }

}

優(yōu)點(diǎn):懶加載鱼填,線程安全,效率較高毅戈,實(shí)現(xiàn)簡(jiǎn)單

靜態(tài)內(nèi)部類單例是如何實(shí)現(xiàn)懶加載的呢苹丸?首先愤惰,我們先了解下類的加載時(shí)機(jī)。

虛擬機(jī)規(guī)范要求有且只有5種情況必須立即對(duì)類進(jìn)行初始化(加載赘理、驗(yàn)證宦言、準(zhǔn)備需要在此之前開(kāi)始):

  1. 遇到 newgetstatic感憾、putstatic蜡励、invokestatic 這4條字節(jié)碼指令時(shí)令花。生成這4條指令最常見(jiàn)的 Java 代碼場(chǎng)景是:使用 new 關(guān)鍵字實(shí)例化對(duì)象的時(shí)候阻桅、讀取或設(shè)置一個(gè)類的靜態(tài)字段(final修飾除外,被final修飾的靜態(tài)字段是常量兼都,已在編譯期把結(jié)果放入常量池)的時(shí)候嫂沉,以及調(diào)用一個(gè)類的靜態(tài)方法的時(shí)候。

  2. 使用 java.lang.reflect 包方法對(duì)類進(jìn)行反射調(diào)用的時(shí)候扮碧。

  3. 當(dāng)初始化一個(gè)類的時(shí)候趟章,如果發(fā)現(xiàn)其父類還沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其父類的初始化慎王。

  4. 當(dāng)虛擬機(jī)啟動(dòng)時(shí)蚓土,用戶需要指定一個(gè)要執(zhí)行的主類(包含main()的那個(gè)類),虛擬機(jī)會(huì)先初始化這個(gè)主類赖淤。

  5. 當(dāng)使用JDK 1.7的動(dòng)態(tài)語(yǔ)言支持時(shí)蜀漆,如果一個(gè) java.lang.invoke.MethodHandle 實(shí)例最后的解析結(jié)果是REF_getStaticREF_putStatic咱旱、REF_invokeStatic 的方法句柄确丢,則需要先觸發(fā)這個(gè)方法句柄所對(duì)應(yīng)的類的初始化。

這5種情況被稱為是類的主動(dòng)引用吐限,注意鲜侥,這里《虛擬機(jī)規(guī)范》中使用的限定詞是 "有且僅有",那么诸典,除此之外的所有引用類都不會(huì)對(duì)類進(jìn)行初始化描函,稱為被動(dòng)引用。靜態(tài)內(nèi)部類就屬于被動(dòng)引用的情況狐粱。

當(dāng)getInstance()方法被調(diào)用時(shí)舀寓,SingleTonHoler才在SingleTon的運(yùn)行時(shí)常量池里,把符號(hào)引用替換為直接引用脑奠,這時(shí)靜態(tài)對(duì)象INSTANCE也真正被創(chuàng)建基公,然后再被getInstance()方法返回出去,這點(diǎn)同餓漢模式宋欺。

那么 INSTANCE 在創(chuàng)建過(guò)程中又是如何保證線程安全的呢轰豆?在《深入理解JAVA虛擬機(jī)》中胰伍,有這么一句話:

虛擬機(jī)會(huì)保證一個(gè)類的 <clinit>() 方法在多線程環(huán)境中被正確地加鎖、同步酸休,如果多個(gè)線程同時(shí)去初始化一個(gè)類骂租,那么只會(huì)有一個(gè)線程去執(zhí)行這個(gè)類的 <clinit>() 方法,其他線程都需要阻塞等待斑司,直到活動(dòng)線程執(zhí)行 <clinit>() 方法完畢渗饮。如果在一個(gè)類的 <clinit>() 方法中有耗時(shí)很長(zhǎng)的操作,就可能造成多個(gè)進(jìn)程阻塞(需要注意的是宿刮,其他線程雖然會(huì)被阻塞互站,但如果執(zhí)行<clinit>()方法后,其他線程喚醒之后不會(huì)再次進(jìn)入<clinit>()方法僵缺。同一個(gè)加載器下胡桃,一個(gè)類型只會(huì)初始化一次。)磕潮,在實(shí)際應(yīng)用中翠胰,這種阻塞往往是很隱蔽的。

從上面的分析可以看出INSTANCE在創(chuàng)建過(guò)程中是線程安全的自脯,所以說(shuō)靜態(tài)內(nèi)部類形式的單例可保證線程安全之景,也能保證單例的唯一性,同時(shí)也延遲了單例的實(shí)例化膏潮。

6锻狗、枚舉單例

代碼實(shí)現(xiàn)如下:

/**
 * 枚舉實(shí)現(xiàn)單例
 *
 * @className: Singleton
 * @date: 2021/6/7 14:32
 */
public enum Singleton {
    INSTANCE;
    public void doSomething(String str) {
        System.out.println(str);
    }
}

優(yōu)點(diǎn):簡(jiǎn)單,高效戏罢,線程安全屋谭,可以避免通過(guò)反射破壞枚舉單例

枚舉在java中與普通類一樣,都能擁有字段與方法龟糕,而且枚舉實(shí)例創(chuàng)建是線程安全的桐磁,在任何情況下,它都是一個(gè)單例讲岁,可以直接通過(guò)如下方式調(diào)用獲取實(shí)例:

Singleton singleton = Singleton.INSTANCE;

使用下面的命令反編譯枚舉類

javap Singleton.class

得到如下內(nèi)容

Compiled from "Singleton.java"
public final class com.spring.demo.singleton.Singleton extends java.lang.Enum<com.spring.demo.singleton.Singleton> {
  public static final com.spring.demo.singleton.Singleton INSTANCE;
  public static com.spring.demo.singleton.Singleton[] values();
  public static com.spring.demo.singleton.Singleton valueOf(java.lang.String);
  public void doSomething(java.lang.String);
  static {};
}

從枚舉的反編譯結(jié)果可以看到我擂,INSTANCE 被 static final修飾,所以可以通過(guò)類名直接調(diào)用缓艳,并且創(chuàng)建對(duì)象的實(shí)例是在靜態(tài)代碼塊中創(chuàng)建的校摩,因?yàn)?static 類型的屬性會(huì)在類被加載之后被初始化,當(dāng)一個(gè)Java類第一次被真正使用到的時(shí)候靜態(tài)資源被初始化阶淘、Java類的加載和初始化過(guò)程都是線程安全的衙吩,所以創(chuàng)建一個(gè)enum類型是線程安全的。

通過(guò)反射破壞枚舉溪窒,實(shí)現(xiàn)代碼如下:

public class Test {
    public static void main(String[] args) throws Exception {
        Singleton singleton = Singleton.INSTANCE;
        singleton.doSomething("hello enum");

        // 嘗試使用反射破壞單例
        // 枚舉類沒(méi)有空參構(gòu)造方法坤塞,反編譯后可以看到枚舉有一個(gè)兩個(gè)參數(shù)的構(gòu)造方法
        Constructor<Singleton> declaredConstructor = Singleton.class.getDeclaredConstructor(String.class, int.class);
        // 設(shè)置強(qiáng)制訪問(wèn)
        declaredConstructor.setAccessible(true);
        // 創(chuàng)建實(shí)例冯勉,這里會(huì)報(bào)錯(cuò),因?yàn)闊o(wú)法通過(guò)反射創(chuàng)建枚舉的實(shí)例
        Singleton enumSingleton = declaredConstructor.newInstance();
        System.out.println(enumSingleton);
    }
}

運(yùn)行結(jié)果報(bào)如下錯(cuò)誤:

Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
    at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:492)
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
    at com.spring.demo.singleton.Test.main(Test.java:24)

查看反射創(chuàng)建實(shí)例的newInstance()方法摹芙,有如下判斷:

image.png

所以無(wú)法通過(guò)反射創(chuàng)建枚舉的實(shí)例灼狰。

六、總結(jié)

本文簡(jiǎn)單介紹了單例設(shè)計(jì)模式的幾種實(shí)現(xiàn)方式浮禾,除了枚舉單例交胚,其他的所有實(shí)現(xiàn)都可以通過(guò)反射破壞單例模式,在《effective java》中推薦枚舉實(shí)現(xiàn)單例模式盈电,在實(shí)際場(chǎng)景中使用哪一種單例實(shí)現(xiàn)蝴簇,需要根據(jù)自己的情況選擇,適合當(dāng)前場(chǎng)景的才是比較好的方式挣轨。

參考文章

https://blog.csdn.net/mnb65482/article/details/80458571

https://www.oodesign.com/singleton-pattern.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末军熏,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子卷扮,更是在濱河造成了極大的恐慌,老刑警劉巖均践,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晤锹,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡彤委,警方通過(guò)查閱死者的電腦和手機(jī)鞭铆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)焦影,“玉大人车遂,你說(shuō)我怎么就攤上這事∷钩剑” “怎么了舶担?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)彬呻。 經(jīng)常有香客問(wèn)我衣陶,道長(zhǎng),這世上最難降的妖魔是什么闸氮? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任剪况,我火速辦了婚禮,結(jié)果婚禮上蒲跨,老公的妹妹穿的比我還像新娘译断。我一直安慰自己,他們只是感情好或悲,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布孙咪。 她就那樣靜靜地躺著藏姐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪该贾。 梳的紋絲不亂的頭發(fā)上羔杨,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音杨蛋,去河邊找鬼兜材。 笑死,一個(gè)胖子當(dāng)著我的面吹牛逞力,可吹牛的內(nèi)容都是我干的曙寡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼寇荧,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼举庶!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起揩抡,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤户侥,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后峦嗤,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蕊唐,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年烁设,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了替梨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡装黑,死狀恐怖副瀑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情恋谭,我是刑警寧澤糠睡,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站箕别,受9級(jí)特大地震影響铜幽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜串稀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一除抛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧母截,春花似錦到忽、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)护蝶。三九已至,卻和暖如春翩迈,著一層夾襖步出監(jiān)牢的瞬間持灰,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工负饲, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留堤魁,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓返十,卻偏偏與公主長(zhǎng)得像妥泉,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子洞坑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容