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

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


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

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

應(yīng)用場景

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

單例優(yōu)缺點

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

缺點:
a、不適用于變化的對象
b茫虽、沒有抽象層刊苍,所以擴展難度很大
c、職責(zé)過重濒析,違背了單一職責(zé)原則
d正什、濫用容易導(dǎo)出溢出或丟失情況

單例模式的種類




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

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

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


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

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

單例模式如何破壞

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

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

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

b、利用序列化呜师,將序列化后的結(jié)果存入硬盤娶桦,然后再次反序列化,等到的結(jié)果就和原先的不一致

序列化:將存放在內(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);---獲取當(dāng)前類的超類(沒有實現(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("我是無參構(gòu)造函數(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方法墨吓,且沒有默認(rèn)的無參構(gòu)造函數(shù)球匕。
A、底層轉(zhuǎn)換類繼承Enum
B帖烘、使用靜態(tài)代碼快方式亮曹,當(dāng)靜態(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"));
        /**
         * 方法點進(jìn)去后可查看:
         * 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);

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市秘症,隨后出現(xiàn)的幾起案子照卦,更是在濱河造成了極大的恐慌,老刑警劉巖乡摹,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件役耕,死亡現(xiàn)場離奇詭異,居然都是意外死亡聪廉,警方通過查閱死者的電腦和手機瞬痘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來板熊,“玉大人框全,你說我怎么就攤上這事「汕” “怎么了津辩?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長筒严。 經(jīng)常有香客問我,道長情萤,這世上最難降的妖魔是什么鸭蛙? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮筋岛,結(jié)果婚禮上娶视,老公的妹妹穿的比我還像新娘。我一直安慰自己睁宰,他們只是感情好肪获,可當(dāng)我...
    茶點故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著柒傻,像睡著了一般孝赫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上红符,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天青柄,我揣著相機與錄音伐债,去河邊找鬼。 笑死致开,一個胖子當(dāng)著我的面吹牛峰锁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播双戳,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼虹蒋,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了飒货?” 一聲冷哼從身側(cè)響起玖雁,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎帜篇,沒想到半個月后中剩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡莫辨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年傲茄,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沮榜。...
    茶點故事閱讀 39,764評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡盘榨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蟆融,到底是詐尸還是另有隱情草巡,我是刑警寧澤,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布型酥,位于F島的核電站山憨,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏弥喉。R本人自食惡果不足惜郁竟,卻給世界環(huán)境...
    茶點故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望由境。 院中可真熱鬧棚亩,春花似錦、人聲如沸虏杰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽纺阔。三九已至瘸彤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間笛钝,已是汗流浹背钧栖。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工低零, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拯杠。 一個月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓掏婶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親潭陪。 傳聞我的和親對象是個殘疾皇子雄妥,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,665評論 2 354