設計模式-單例模式(Singleton Pattern)

上一篇 <<<觀察者模式(Observer Pattern)
下一篇 >>>創(chuàng)建對象的方式匯總


單例模式:一個類僅有一個實例,并提供一個訪問它的全局訪問點。

  • 優(yōu)點:減少代碼冗余、提高代碼復用性、安全性、隱藏真實角色、非入侵、節(jié)約內(nèi)存、重復利用
  • 缺點:線程安全問題,數(shù)量很多的話容易導致內(nèi)存泄露

應用場景

  • spring IOC容器
  • 線程池(數(shù)據(jù)庫、多線程)
  • 枚舉、常量類
  • 配置文件常量
  • 日志
  • HttpApplication、servlet
  • windows系統(tǒng)的任務管理器、回收站、網(wǎng)站計數(shù)器、顯卡驅(qū)動、打印機等

單例優(yōu)缺點

優(yōu)點:
a、防止其他對象自身的實例化,保證所有的對象都訪問一個實例
b、類在實例化進程上有一定的伸縮性
c、提供了對唯一實例的受控訪問
d、節(jié)約系統(tǒng)創(chuàng)建和銷毀對象的資源
e、允許對共享資源的多重占用

缺點:
a、不適用于變化的對象
b、沒有抽象層,所以擴展難度很大
c、職責過重,違背了單一職責原則
d、濫用容易導出溢出或丟失情況

單例模式的種類




第一個if是判斷實例對象是否存在,針對讀寫都有的操作。
第二個if是對同時進行初始化操作的多個線程進入鎖狀態(tài)的再次判斷,如果前面已經(jīng)有創(chuàng)建過的話,將不再實例化,徹底解決單例問題。

靜態(tài)內(nèi)部類方式與雙重檢驗鎖的區(qū)別
雙重檢驗鎖是采用了懶漢式并且只針對寫操作加了鎖,保證了線程的安全又加快了讀的操作。但如果多線程進來的時候,寫操作會存在阻塞的現(xiàn)象,效率不高。

靜態(tài)內(nèi)部類是在使用時才會被初始化,但內(nèi)部類又使用了餓漢式的模式,所以既擁有餓漢式的線程安全,又支持懶漢式的資源不浪費,不存在線程阻塞的情況,比雙重檢驗鎖更加的高效。


單例模式創(chuàng)建方式如何選擇

  • 如果不需要延遲加載單例,可以使用枚舉或者餓漢式,相對來說枚舉性好于餓漢式。
  • 如果需要延遲加載,可以使用靜態(tài)內(nèi)部類或者懶漢式,相對來說靜態(tài)內(nèi)部類好于懶漢式。

單例模式如何破壞

a、利用反射機制,通過declaredConstructors.setAccessible(true);方法即可破解構造函數(shù)私有化的缺陷

Class<?> classInfo = Class.forName("com.jgspx.singleton.crack.regex.RegixObject");
Constructor declaredConstructors = classInfo.getDeclaredConstructor();
declaredConstructors.setAccessible(true);
Object o = declaredConstructors.newInstance();
  • 破解:在構造函數(shù)里判斷如果已經(jīng)實例化的話就拋出異常,防止多次被實例化
public class RegixObject {
    /**
     * 如果是餓漢式,則反射機制調(diào)用構造函數(shù)的時候就會報錯
     */
    private static RegixObject regixObject = new RegixObject();

    public static  RegixObject getInstance(){
        /**
         * 如果是懶漢式,先利用反射,然后代碼調(diào)用,則構造函數(shù)里加上判斷也沒用
         */
        if(regixObject==null){
            regixObject = new RegixObject();
        }
        return regixObject;
    }
    private RegixObject(){
        //加上這一句話,可破解多次初始化的情況
        if(regixObject!=null){
            throw new RuntimeException("初始化已執(zhí)行過");
        }
    }
}

b、利用序列化,將序列化后的結果存入硬盤,然后再次反序列化,等到的結果就和原先的不一致

序列化:將存放在內(nèi)存中的對象信息序列操作后變成可以存放在硬盤的數(shù)據(jù)。
反序列化:將硬盤上的數(shù)據(jù)解析后放入到內(nèi)存中。

public static void main(String[] args) throws Exception{
    User instance = User.getInstance();
    FileOutputStream fos = new FileOutputStream("./user.obj");
    ObjectOutputStream oos = new ObjectOutputStream(fos);
    oos.writeObject(instance);
    oos.flush();
    oos.close();

    FileInputStream fis = new FileInputStream("./user.obj");
    ObjectInputStream ois = new ObjectInputStream(fis);
    User singleton2 = (User) ois.readObject();
    System.out.println(singleton2==instance);
}
  • 破解:在原有類中增加方法readResolve()
public class User implements Serializable {
    public static User user = new User();

    public static User getInstance(){
        return  user;
    }

    //返回序列化獲取對象 ,保證為單例
    public Object readResolve() {
        return user;
    }
}
  • ObjectInputStream
  • case TC_OBJECT:return checkResolve(readOrdinaryObject(unshared)); *
  • ObjectStreamClass desc = readClassDesc(false);---獲取當前類的超類(沒有實現(xiàn)Serializable的)。eg:User extend A,且A沒有實現(xiàn)Serializable,則desc=A.class,否則desc=Object.class
  • if (desc.hasReadResolveMethod()){Object rep = desc.invokeReadResolve(obj);}

最強單例模式--枚舉

a. 枚舉單例源碼

public enum  SingleV6 {
    TT;

    SingleV6(){
        System.out.println("我是無參構造函數(shù)被執(zhí)行到了");
    }

    public void add(){
        System.out.println("添加操作被啟動");
    }
}

b. 枚舉單例反編譯后的代碼

public final class SingleV6 extends Enum
{

    public static SingleV6[] values()
    {
        return (SingleV6[])$VALUES.clone();
    }

    public static SingleV6 valueOf(String name)
    {
        return (SingleV6)Enum.valueOf(com/jarye/singleton/v6/SingleV6, name);
    }

    private SingleV6(String s, int i)
    {
        super(s, i);
        System.out.println("\u6211\u662F\u65E0\u53C2\u6784\u9020\u51FD\u6570\u88AB\u6267\u884C\u5230\u4E86");
    }

    public void add()
    {
        System.out.println("\u6DFB\u52A0\u64CD\u4F5C\u88AB\u542F\u52A8");
    }

    public static final SingleV6 TT;
    private static final SingleV6 $VALUES[];

    static 
    {
        TT = new SingleV6("TT", 0);
        $VALUES = (new SingleV6[] {
            TT
        });
    }
}

枚舉類型其實就是class類,繼承于Enum類,內(nèi)置了name、ordinal和values方法,且沒有默認的無參構造函數(shù)。
A、底層轉(zhuǎn)換類繼承Enum
B、使用靜態(tài)代碼快方式,當靜態(tài)代碼快執(zhí)行的時候初始化該對象

c. 枚舉單例無法被破解的原因

  • 無參方式破解
Class<?> classInfo = Class.forName("com.jarye.singleton.v6.SingleV6");
Constructor<?> declaredConstructor = classInfo.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
declaredConstructor.newInstance();
  • 參照源碼的有參方式破解
Class<?> classInfo2 = Class.forName("com.jarye.singleton.v6.SingleV6");
Constructor<?> declaredConstructor2 = classInfo2.getDeclaredConstructor(String.class,Integer.class);
declaredConstructor2.setAccessible(true);
declaredConstructor2.newInstance("zhangsan",3);

d. 序列化破解失敗原因

Singleton06 instance = Singleton06.INSTANCE;
        FileOutputStream fos = new FileOutputStream("./a.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        Singleton06 singleton3 = Singleton06.INSTANCE;
        oos.writeObject(singleton3);
        oos.close();
        fos.close();
        System.out.println("----------從硬盤中反序列化對象到內(nèi)存中------------");
        //2.從硬盤中反序列化對象到內(nèi)存中
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("./a.txt"));
        /**
         * 方法點進去后可查看:
         * case TC_ENUM:
         *                     return checkResolve(readEnum(unshared));
         *   readEnum走的是:Enum<?> en = Enum.valueOf((Class)cl, name);
         *   最后是從map中取出的值
         *   Map<String, T> enumConstantDirectory
         *
         */
        // 從新獲取一個新的對象
        Singleton06 singleton4 = (Singleton06) ois.readObject();
        System.out.println(instance == singleton4);

相關文章鏈接:
<<<23種常用設計模式總覽
<<<代理模式(Proxy Pattern)
<<<裝飾模式(Decorator Pattern)
<<<觀察者模式(Observer Pattern)
<<<責任鏈模式(Chain of Responsibility Pattern)
<<<策略模式(Strategy Pattern)
<<<模板方法模式(Template Pattern)
<<<外觀/門面模式(Facade Pattern)
<<<建造者模式(Builder Pattern)
<<<適配器模式(Adapter Pattern)
<<<原型模式(Prototype Pattern)
<<<工廠相關模式(Factory Pattern)

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容