亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java單例模式與破壞單例模式概念原理深入講解

 更新時(shí)間:2023年02月21日 10:34:33   作者:綠仔牛奶_  
單例模式(Singleton?Pattern)是?Java?中最簡(jiǎn)單的設(shè)計(jì)模式之一。這種類(lèi)型的設(shè)計(jì)模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對(duì)象的最佳方式。這種模式涉及到一個(gè)單一的類(lèi),該類(lèi)負(fù)責(zé)創(chuàng)建自己的對(duì)象,同時(shí)確保只有單個(gè)對(duì)象被創(chuàng)建

什么是單例模式

經(jīng)典設(shè)計(jì)模式又分23種,也就是GoF 23 總體分為三大類(lèi):

  • 創(chuàng)建型模式
  • 結(jié)構(gòu)性模式
  • 行為型模式

Java中單例模式是一種常見(jiàn)的設(shè)計(jì)模式,單例模式的寫(xiě)法有好幾種,這里主要介紹三種:懶漢式單例、餓漢式單例、登記式單例。

單例模式有以下特點(diǎn):

  • 單例類(lèi)只能有一個(gè)實(shí)例。
  • 單例類(lèi)必須自己創(chuàng)建自己的唯一實(shí)例。
  • 單例類(lèi)必須給所有其他對(duì)象提供這一實(shí)例。

  單例模式確保某個(gè)類(lèi)只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。在計(jì)算機(jī)系統(tǒng)中,線程池、緩存、日志對(duì)象、對(duì)話框、打印機(jī)、顯卡的驅(qū)動(dòng)程序?qū)ο蟪1辉O(shè)計(jì)成單例。這些應(yīng)用都或多或少具有資源管理器的功能。每臺(tái)計(jì)算機(jī)可以有若干個(gè)打印機(jī),但只能有一個(gè)Printer Spooler,以避免兩個(gè)打印作業(yè)同時(shí)輸出到打印機(jī)中。每臺(tái)計(jì)算機(jī)可以有若干通信端口,系統(tǒng)應(yīng)當(dāng)集中管理這些通信端口,以避免一個(gè)通信端口同時(shí)被兩個(gè)請(qǐng)求同時(shí)調(diào)用??傊?,選擇單例模式就是為了避免不一致?tīng)顟B(tài)。

餓漢式(預(yù)加載)

餓漢式單例: 在類(lèi)加載時(shí),就會(huì)創(chuàng)建好將會(huì)使用的對(duì)象,可能會(huì)造成內(nèi)存的浪費(fèi)

示例:

public class Hungry {
    // 創(chuàng)建唯一實(shí)例
    private final static Hungry HUNGRY = new Hungry();
    private Hungry(){}
	// 全局訪問(wèn)點(diǎn) ---> 拿到HUNGRY實(shí)例
    public static Hungry getIntance(){
        return HUNGRY;
    }
}

而預(yù)加載就是先一步加載,我們沒(méi)有使用該單例對(duì)象但是已經(jīng)將其加載到內(nèi)存中,那么就會(huì)造成內(nèi)存的浪費(fèi)

懶漢式(懶加載)

懶漢式改善了餓漢式浪費(fèi)內(nèi)存的問(wèn)題,等到需要用到實(shí)例的時(shí)候再去加載到內(nèi)存中

懶漢式寫(xiě)法( 線程不安全 ):

public class LazyMan {
    private LazyMan(){}
    public static LazyMan lazyMan;
    public static LazyMan getInstance(){
        if (lazyMan==null){
           lazyMan = new LazyMan();
        }
        return lazyMan;
    }
}

但是在不進(jìn)行任何同步干預(yù)的情況下,懶漢式不是線程安全的單例模式,經(jīng)典的解決方案就是利用雙重檢驗(yàn)鎖保證程序的原子性有序性,如下示例:

public class LazyMan {
    private LazyMan(){}
    // 懶漢當(dāng)中的雙重檢驗(yàn)鎖 --> 可以保證線程安全
    public volatile static LazyMan lazyMan;
    // volatile 保證了new實(shí)例時(shí)不會(huì)發(fā)生指令重排
    public static LazyMan getInstance(){
        if (lazyMan==null){
            synchronized (LazyMan.class){
                // 此處上鎖  以保證原子操作
                if (lazyMan == null){
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }
}

反射破壞單例模式

反射是一種動(dòng)態(tài)獲取類(lèi)資源的一種途徑,我們讓然可以通過(guò)反射來(lái)獲取單例模式中的更多實(shí)例:

public class LazyMan {
    // 空參構(gòu)造器
    private LazyMan(){}
    // 懶漢當(dāng)中的雙重檢驗(yàn)鎖 --> 可以保證線程安全
    public volatile static LazyMan lazyMan;
    // volatile 保證了new實(shí)例時(shí)不會(huì)發(fā)生指令重排
    public static LazyMan getInstance(){
        if (lazyMan==null){
            synchronized (LazyMan.class){
                // 此處上鎖  以保證原子操作
                if (lazyMan == null){
                    lazyMan = new LazyMan();// 不是原子操作
                }
            }
        }
        return lazyMan;
    }
    public static void main(String[] args) throws Exception{
        // 獲取無(wú)參構(gòu)造器
        Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);// 無(wú)視私有
        // 獲取實(shí)例
        LazyMan instance2 = constructor.newInstance();
        LazyMan instance3 = constructor.newInstance();
        LazyMan instance4 = constructor.newInstance();
        // 懶漢式單例 獲取唯一實(shí)例
        LazyMan instance = LazyMan.getInstance();
        System.out.println("getIntance獲取實(shí)例(1)hashCode:"+instance.hashCode());
        System.out.println("反射構(gòu)造器newIntance獲取實(shí)例(2)hashCode:"+instance2.hashCode());
        System.out.println("反射構(gòu)造器newIntance獲取實(shí)例(3)hashCode:"+instance3.hashCode());
        System.out.println("反射構(gòu)造器newIntance獲取實(shí)例(4)hashCode:"+instance4.hashCode());
    }
}

上述程序輸出結(jié)果如下:

/*
getIntance獲取實(shí)例(1)hashCode:895328852
反射構(gòu)造器newIntance獲取實(shí)例(2)hashCode:1304836502
反射構(gòu)造器newIntance獲取實(shí)例(3)hashCode:225534817
反射構(gòu)造器newIntance獲取實(shí)例(4)hashCode:1878246837
*/

修復(fù)方式1:

// 對(duì)空參構(gòu)造器進(jìn)行上鎖 并對(duì)唯一實(shí)例lazyman判斷是否已經(jīng)初始化
 private LazyMan(){
    if (lazyMan != null){
		throw new RuntimeException("不要試圖破壞單例模式");
    }
 }

但是這種修復(fù)方式仍然會(huì)被破壞,我們首先是利用了反射來(lái)獲取LazyMan的空參構(gòu)造器,并利用其構(gòu)造器進(jìn)行初始化獲取實(shí)例,但是如果我們一直不調(diào)用getIntance方法來(lái)初始化lazyman實(shí)例而一直用反射獲取,那么這種方式就形同虛設(shè)

因此,得出下一個(gè)修復(fù)方式。我們依然對(duì)空參構(gòu)造器進(jìn)行上鎖,然后利用標(biāo)志位保證我們的空參構(gòu)造器只能使用一次,也就是最多只能為一個(gè)實(shí)例進(jìn)行初始化。

修復(fù)方式2:

// 解決2.  對(duì)空參構(gòu)造器進(jìn)行上鎖  利用標(biāo)志位保證空參構(gòu)造器只能初始化一次實(shí)例  但是標(biāo)志位字段仍可以通過(guò)其他途徑被拿到  并且修改
    private static boolean flag = false;
	private LazyMan(){
        synchronized(LazyMan.class){
            if (flag == false){
                flag = true;
            }else {
                throw new RuntimeException("不要試圖破壞單例模式");
            }
        }
    }

上述代碼所示,利用flag作為標(biāo)志位來(lái)保證空參構(gòu)造器只能對(duì)最多一個(gè)實(shí)例執(zhí)行初始化操作。但是,同時(shí)我們所設(shè)置的標(biāo)志位flag同樣存在被通過(guò)各種渠道拿到的風(fēng)險(xiǎn),比如反編譯。拿到flag標(biāo)志后就可以對(duì)其修改,示例:

public static void main(String[] args) throws Exception{
        // 獲取無(wú)參構(gòu)造器
        Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);// 無(wú)視私有
        // 懶漢式單例 獲取唯一實(shí)例
        LazyMan instance = LazyMan.getInstance();  // (1)
        // 獲取標(biāo)志位字段并進(jìn)行修改
        Field flag1 = LazyMan.class.getDeclaredField("flag");
        // (1) 處已經(jīng)調(diào)用了空參構(gòu)造器  flag變?yōu)閠rue  此處修改為false 可以繼續(xù)創(chuàng)建實(shí)例
        flag1.set(instance,false);
        LazyMan instance2 = constructor.newInstance();
        // 與上述同理
        flag1.set(instance2,false);
        LazyMan instance3 = constructor.newInstance();
        System.out.println("getIntance獲取實(shí)例(1)hashCode:"+instance.hashCode());
        System.out.println("反射構(gòu)造器newIntance獲取實(shí)例(2)hashCode:"+instance2.hashCode());
        System.out.println("反射構(gòu)造器newIntance獲取實(shí)例(3)hashCode:"+instance3.hashCode());
    }

那么既然如此,是不是單例程序無(wú)論如何設(shè)計(jì)最終都會(huì)被反射破壞呢?

事實(shí)并非如此,我們打開(kāi)反射得到的構(gòu)造器.newInstance方法源碼查看:

// 我們只看如下兩行
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");

如上述代碼所示,Java給出的解釋為:

如果實(shí)際參數(shù)和形式參數(shù)的數(shù)量不同;如果原始參數(shù)的展開(kāi)轉(zhuǎn)換失?。换蛘呷绻诳赡苷归_(kāi)之后,參數(shù)值不能通過(guò)方法調(diào)用轉(zhuǎn)換轉(zhuǎn)換為相應(yīng)的形式參數(shù)類(lèi)型;如果此構(gòu)造函數(shù)屬于枚舉類(lèi)型。符合上述任一情況將會(huì)拋出IllegalArgumentException("Cannot reflectively create enum objects")非法參數(shù)異常

也就是說(shuō),枚舉類(lèi)型是可以避免單例模式被破壞的

public enum enumSingle {
    INSTANCE;
    public enumSingle getInstance() {
        return INSTANCE;
    }
}
class TestEnumSingle{
    public static void main(String[] args) throws Exception {
        // 下面我們嘗試用反射來(lái)破壞枚舉類(lèi)
        // 枚舉類(lèi)的構(gòu)造器實(shí)際上帶有兩個(gè)參數(shù) String和int
        Constructor<enumSingle> declaredConstructor = enumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        // 直接獲取實(shí)例
        enumSingle instance = enumSingle.INSTANCE;
        // 反射獲取實(shí)例
        enumSingle enumSingle1 = declaredConstructor.newInstance();
        System.out.println("類(lèi)名直接訪問(wèn)獲取實(shí)例hashCode:"+instance.hashCode());
        System.out.println("反射實(shí)例hashCode:"+enumSingle1.hashCode());
    }
}
// 最終拋出  java.lang.IllegalArgumentException: Cannot reflectively create enum objects

除了反射會(huì)打破單例之外,序列化Serializable也同樣會(huì)破壞單例模式,具體體現(xiàn)是物品們同一對(duì)象在序列化前和反序列化之后不是同一對(duì)象

到此這篇關(guān)于Java單例模式與破壞單例模式概念原理深入講解的文章就介紹到這了,更多相關(guān)Java單例模式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringBoot中@RestControllerAdvice注解的使用

    SpringBoot中@RestControllerAdvice注解的使用

    這篇文章主要介紹了SpringBoot中@RestControllerAdvice注解的使用,@RestControllerAdvice主要用精簡(jiǎn)客戶端返回異常,它可以捕獲各種異常,需要的朋友可以參考下
    2024-01-01
  • 6個(gè)必備的Java并發(fā)面試種子題目合集

    6個(gè)必備的Java并發(fā)面試種子題目合集

    并發(fā)是Java面試的經(jīng)常會(huì)考到的知識(shí)點(diǎn),這篇文章主要為大家整理了6個(gè)必備的Java并發(fā)面試種子題目,文中的示例代碼簡(jiǎn)潔易懂,需要的可以學(xué)習(xí)一下
    2023-07-07
  • 在springboot中使用攔截器的步驟詳解

    在springboot中使用攔截器的步驟詳解

    攔截器Interceptor,是SpringMVC中的核心內(nèi)容,在SpringBoot中使用Interceptor,同時(shí)采用全注解開(kāi)發(fā),這篇文章主要介紹了在springboot中使用攔截器的步驟,需要的朋友可以參考下
    2022-01-01
  • java設(shè)計(jì)模式:建造者模式之生產(chǎn)線

    java設(shè)計(jì)模式:建造者模式之生產(chǎn)線

    這篇文章主要介紹了Java設(shè)計(jì)模式之建造者模式,結(jié)合具體實(shí)例形式分析了建造者模式的概念、原理、實(shí)現(xiàn)方法與相關(guān)使用注意事項(xiàng),需要的朋友可以參考下
    2021-08-08
  • SpringBoot中@ConditionalOnBean實(shí)現(xiàn)原理解讀

    SpringBoot中@ConditionalOnBean實(shí)現(xiàn)原理解讀

    這篇文章主要介紹了SpringBoot中@ConditionalOnBean實(shí)現(xiàn)原理,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • SpringMVC中@RequestMapping注解的實(shí)現(xiàn)

    SpringMVC中@RequestMapping注解的實(shí)現(xiàn)

    RequestMapping是一個(gè)用來(lái)處理請(qǐng)求地址映射的注解,本文主要介紹了SpringMVC中@RequestMapping注解的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-01-01
  • Java類(lèi)加載器ClassLoader源碼層面分析講解

    Java類(lèi)加載器ClassLoader源碼層面分析講解

    ClassLoader翻譯過(guò)來(lái)就是類(lèi)加載器,普通的java開(kāi)發(fā)者其實(shí)用到的不多,但對(duì)于某些框架開(kāi)發(fā)者來(lái)說(shuō)卻非常常見(jiàn)。理解ClassLoader的加載機(jī)制,也有利于我們編寫(xiě)出更高效的代碼。ClassLoader的具體作用就是將class文件加載到j(luò)vm虛擬機(jī)中去,程序就可以正確運(yùn)行了
    2022-09-09
  • java基礎(chǔ)開(kāi)發(fā)泛型類(lèi)的詳解

    java基礎(chǔ)開(kāi)發(fā)泛型類(lèi)的詳解

    這篇文章為大家介紹了java基礎(chǔ)開(kāi)發(fā)中泛型類(lèi)的詳解,包括泛型的概念以及應(yīng)用實(shí)例有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2021-10-10
  • springboot之SpringApplication生命周期和事件機(jī)制解讀

    springboot之SpringApplication生命周期和事件機(jī)制解讀

    這篇文章主要介紹了springboot之SpringApplication生命周期和事件機(jī)制,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • Java Socket+mysql實(shí)現(xiàn)簡(jiǎn)易文件上傳器的代碼

    Java Socket+mysql實(shí)現(xiàn)簡(jiǎn)易文件上傳器的代碼

    最近在做一個(gè)小項(xiàng)目,項(xiàng)目主要需求是實(shí)現(xiàn)一個(gè)文件上傳器,通過(guò)客戶端的登陸,把本地文件上傳到服務(wù)器的數(shù)據(jù)庫(kù)(本地的)。下面通過(guò)本文給大家分享下實(shí)現(xiàn)代碼,感興趣的朋友一起看看吧
    2016-10-10

最新評(píng)論