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

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

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

什么是單例模式

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

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

Java中單例模式是一種常見的設計模式,單例模式的寫法有好幾種,這里主要介紹三種:懶漢式單例、餓漢式單例、登記式單例。

單例模式有以下特點:

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

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

餓漢式(預加載)

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

示例:

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

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

懶漢式(懶加載)

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

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

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

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

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

反射破壞單例模式

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

public class LazyMan {
    // 空參構(gòu)造器
    private LazyMan(){}
    // 懶漢當中的雙重檢驗鎖 --> 可以保證線程安全
    public volatile static LazyMan lazyMan;
    // volatile 保證了new實例時不會發(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{
        // 獲取無參構(gòu)造器
        Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);// 無視私有
        // 獲取實例
        LazyMan instance2 = constructor.newInstance();
        LazyMan instance3 = constructor.newInstance();
        LazyMan instance4 = constructor.newInstance();
        // 懶漢式單例 獲取唯一實例
        LazyMan instance = LazyMan.getInstance();
        System.out.println("getIntance獲取實例(1)hashCode:"+instance.hashCode());
        System.out.println("反射構(gòu)造器newIntance獲取實例(2)hashCode:"+instance2.hashCode());
        System.out.println("反射構(gòu)造器newIntance獲取實例(3)hashCode:"+instance3.hashCode());
        System.out.println("反射構(gòu)造器newIntance獲取實例(4)hashCode:"+instance4.hashCode());
    }
}

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

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

修復方式1:

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

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

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

修復方式2:

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

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

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

那么既然如此,是不是單例程序無論如何設計最終都會被反射破壞呢?

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

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

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

如果實際參數(shù)和形式參數(shù)的數(shù)量不同;如果原始參數(shù)的展開轉(zhuǎn)換失??;或者如果在可能展開之后,參數(shù)值不能通過方法調(diào)用轉(zhuǎn)換轉(zhuǎn)換為相應的形式參數(shù)類型;如果此構(gòu)造函數(shù)屬于枚舉類型。符合上述任一情況將會拋出IllegalArgumentException("Cannot reflectively create enum objects")非法參數(shù)異常

也就是說,枚舉類型是可以避免單例模式被破壞的

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

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

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

相關(guān)文章

  • SpringBoot中@RestControllerAdvice注解的使用

    SpringBoot中@RestControllerAdvice注解的使用

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

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

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

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

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

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

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

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

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

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

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

    Java類加載器ClassLoader源碼層面分析講解

    ClassLoader翻譯過來就是類加載器,普通的java開發(fā)者其實用到的不多,但對于某些框架開發(fā)者來說卻非常常見。理解ClassLoader的加載機制,也有利于我們編寫出更高效的代碼。ClassLoader的具體作用就是將class文件加載到jvm虛擬機中去,程序就可以正確運行了
    2022-09-09
  • java基礎開發(fā)泛型類的詳解

    java基礎開發(fā)泛型類的詳解

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

    springboot之SpringApplication生命周期和事件機制解讀

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

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

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

最新評論