最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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)建型模式
  • 結構性模式
  • 行為型模式

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 {
    // 空參構造器
    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{
        // 獲取無參構造器
        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("反射構造器newIntance獲取實例(2)hashCode:"+instance2.hashCode());
        System.out.println("反射構造器newIntance獲取實例(3)hashCode:"+instance3.hashCode());
        System.out.println("反射構造器newIntance獲取實例(4)hashCode:"+instance4.hashCode());
    }
}

上述程序輸出結果如下:

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

修復方式1:

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

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

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

修復方式2:

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

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

public static void main(String[] args) throws Exception{
        // 獲取無參構造器
        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)用了空參構造器  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("反射構造器newIntance獲取實例(2)hashCode:"+instance2.hashCode());
        System.out.println("反射構造器newIntance獲取實例(3)hashCode:"+instance3.hashCode());
    }

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

事實并非如此,我們打開反射得到的構造器.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ù)類型;如果此構造函數(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 {
        // 下面我們嘗試用反射來破壞枚舉類
        // 枚舉類的構造器實際上帶有兩個參數(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)是物品們同一對象在序列化前和反序列化之后不是同一對象

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

相關文章

最新評論

晋宁县| 祥云县| 龙游县| 高要市| 正阳县| 临清市| 普格县| 南京市| 沛县| 会宁县| 碌曲县| 商丘市| 黎城县| 台南市| 昌邑市| 桂阳县| 兰考县| 枣强县| 连山| 云浮市| 西乡县| 方山县| 花莲市| 乌兰浩特市| 千阳县| 兴仁县| 乳山市| 呼图壁县| 丰宁| 德昌县| 灵丘县| 大邑县| 乐清市| 利津县| 张家港市| 吴川市| 邳州市| 象山县| 刚察县| 遂昌县| 广宗县|