【Java設(shè)計(jì)模式】創(chuàng)建型模式-單例模式

源代碼:https://gitee.com/AgentXiao/SingletonPattern

一拆魏、核心作用

保證一個(gè)類只有一個(gè)實(shí)例,并且提供一個(gè)訪問該實(shí)例的全局訪問點(diǎn)邀泉。

二买窟、優(yōu)點(diǎn)

單例模式只生成一個(gè)實(shí)例,減少了系統(tǒng)性能開銷

三贾铝、各種版本的單例模式

實(shí)現(xiàn)單例模式的基本規(guī)則:
(1)構(gòu)造器私有化隙轻,不讓外部創(chuàng)建對象
(2)提供外部調(diào)用的對象的方法

1、餓漢式

餓漢式的特點(diǎn)是“餓”垢揩,即類加載時(shí)便創(chuàng)建對象玖绿。

    //餓漢式:類初始化時(shí)立即加載
    private static SingletonDome01 singleton = new SingletonDome01();
    //構(gòu)造器私有化,外部用戶不能夠創(chuàng)建對象
    private SingletonDome01(){
    }
    //提供訪問方法叁巨,每次訪問返回的都是同一個(gè)對象
    public static SingletonDome01 getSingleton(){
        return singleton;
    }

特點(diǎn):
線程安全的:虛擬機(jī)保證只會(huì)裝載一次該類斑匪,肯定不會(huì)發(fā)生并發(fā)訪問的問題。因锋勺。
問題:
可能造成資源浪費(fèi):如果只是加載本類蚀瘸,而不是要調(diào)用getInstance(),甚至永遠(yuǎn)沒有調(diào)用庶橱,則會(huì)造成資源浪費(fèi)贮勃!

2、懶漢式

懶漢式的特點(diǎn)是“懶”苏章,即類加載的時(shí)候不創(chuàng)建對象寂嘉,在第一次調(diào)用訪問方法時(shí)創(chuàng)建,再次調(diào)用時(shí)直接調(diào)用枫绅。

    //懶漢式:類初始化時(shí)不立即加載
    private static SingletonDome02 singleton;
    //構(gòu)造器私有化泉孩,外部用戶不能夠創(chuàng)建對象
    private SingletonDome02(){
    }
    //提供訪問方法,第一次訪問時(shí)創(chuàng)建撑瞧,之后直接返回棵譬。但是需要設(shè)置同步
    public static synchronized SingletonDome02 getSingleton(){
        if(singleton == null){
            singleton = new SingletonDome02();
        }
        return singleton;
    }

問題:
每次調(diào)用getInstance()方法都要同步,并發(fā)效率較低预伺。在不加synchronized時(shí)订咸,假設(shè)A、B同時(shí)訪問酬诀,A創(chuàng)建了對象脏嚷,但是由于B也是null,也創(chuàng)建了一個(gè)對象瞒御,導(dǎo)致創(chuàng)建了多個(gè)對象父叙,不符合單例的原則。

3、雙重檢測鎖

因?yàn)轲I漢式會(huì)讓整個(gè)方法進(jìn)行等待趾唱,效率低涌乳。因此將同步內(nèi)容下方到if內(nèi)部,提高了執(zhí)行的效率甜癞。不必每次獲取對象時(shí)都進(jìn)行同步夕晓,只有第一次才同步,創(chuàng)建了以后就沒必要了悠咱。

    //懶漢式:類初始化時(shí)不立即加載
    private static SingletonDome03 singleton;
    //構(gòu)造器私有化蒸辆,外部用戶不能夠創(chuàng)建對象
    private SingletonDome03(){
    }
    //提供訪問方法,第一次訪問時(shí)創(chuàng)建析既,之后直接返回
    public static SingletonDome03 getSingleton(){
        if (singleton == null) {
            SingletonDome03 sc;//第一次訪問時(shí)定義一個(gè)對象
            synchronized (SingletonDome03.class) {
                sc = singleton;
                if (sc == null) {
                    synchronized (SingletonDome03.class) {
                        if(sc == null) {
                            sc = new SingletonDome03();
                        }
                    }
                    singleton = sc;
                }
            }
        }
        return singleton;
    }

由于編譯器優(yōu)化原因和JVM底層內(nèi)部模型原因躬贡,偶爾會(huì)出問題。不建議使用眼坏。

4拂玻、靜態(tài)內(nèi)部類實(shí)現(xiàn)

    //靜態(tài)內(nèi)部類不會(huì)立即加載,只有真正調(diào)用時(shí)才會(huì)加載
    private static class SingletonClass{
        private static final SingletonDome04 singleton = new SingletonDome04();
    }
    //構(gòu)造器私有化空骚,外部用戶不能夠創(chuàng)建對象
    private SingletonDome04(){
    }
    //提供訪問方法纺讲,第一次訪問時(shí)創(chuàng)建,之后直接返回囤屹。但是需要設(shè)置同步
    public static synchronized SingletonDome04 getSingleton(){
        return SingletonClass.singleton;
    }

說明:
(1)加載外部類時(shí)不會(huì)直接創(chuàng)建對象熬甚;
(2)只有在調(diào)用訪問方法時(shí)才會(huì)加載靜態(tài)內(nèi)部類去創(chuàng)建對象,同時(shí)加載一次類保證了線程安全肋坚。singleton是static final類型乡括,保證了內(nèi)存中只有這樣一個(gè)實(shí)例存在,而且只能被賦值一次智厌,從而保證了線程安全性诲泌。
(3)兼?zhèn)淞瞬l(fā)高效調(diào)用和延遲加載的優(yōu)勢!

5铣鹏、問題(針對以上四種方式)

(1)反射破解

        SingletonDome02 s1 = SingletonDome02.getSingleton();
        SingletonDome02 s2 = SingletonDome02.getSingleton();
        System.out.println(s1);
        System.out.println(s2);

        System.out.println();

        Class<SingletonDome02> clazz = (Class<SingletonDome02>) Class.forName("pri.xiaowd.singleton.SingletonDome02");
        Constructor<SingletonDome02> c = clazz.getDeclaredConstructor(null);
        c.setAccessible(true);//忽略權(quán)限
        SingletonDome02 s3 = c.newInstance();//構(gòu)造對象
        SingletonDome02 s4 = c.newInstance();//構(gòu)造對象
        System.out.println(s3);
        System.out.println(s4);

由此可見敷扫,利用反射破解了單例模式。

單例破解

通過在私有化的構(gòu)造方法中拋異常的方法可以解決诚卸。如果對象已經(jīng)創(chuàng)建了葵第,則報(bào)錯(cuò)。

    private SingletonDome02(){
        if(singleton != null){
            throw new RuntimeException();
        }
    }

(2)反序列化破解
前提條件是SingletonDome02類實(shí)現(xiàn)了Serializable接口合溺。

        //通過反序列化破解單例
        FileOutputStream fos = new FileOutputStream("d:/a.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(s1);
        oos.close();
        fos.close();

        ObjectInputStream ios = new ObjectInputStream(new FileInputStream("d:/a.txt"));
        SingletonDome02 s3 = (SingletonDome02) ios.readObject();
        System.out.println(s3);
反序列化破解單例

解決辦法:在SingletonDome02類中定義一個(gè)方法.反序列化時(shí)會(huì)自動(dòng)調(diào)用卒密。

    //反序列化時(shí)如果定義了readResolve則直接返回此對象,不需要再創(chuàng)建
    private Object readResolve(){
        return singleton;
    }

6棠赛、枚舉實(shí)現(xiàn)

    //這個(gè)枚舉元素本身就是單例
    SINGLETON;
    
    //自己想要的操作
    public static void getSingleton() {
        
    }

優(yōu)點(diǎn):
– 實(shí)現(xiàn)簡單哮奇。
– 枚舉本身就是單例模式膛腐。由JVM從根本上提供保障!避免通過反射和反序列化的漏洞鼎俘。
缺點(diǎn):
– 無延遲加載哲身。

System.out.println(SingletonDome05.SINGLETON == SingletonDome05.SINGLETON);

返回true。

7贸伐、比較

常見的五種單例模式實(shí)現(xiàn)方式
– 主要:
? 餓漢式(線程安全律罢,調(diào)用效率高。 但是棍丐,不能延時(shí)加載。)
? 懶漢式(線程安全沧踏,調(diào)用效率不高歌逢。 但是,可以延時(shí)加載翘狱。)
– 其他:
? 雙重檢測鎖式(由于JVM底層內(nèi)部模型原因秘案,偶爾會(huì)出問題。不建議使用)
? 靜態(tài)內(nèi)部類式(線程安全潦匈,調(diào)用效率高阱高。 但是,可以延時(shí)加載)
? 枚舉式(線程安全茬缩,調(diào)用效率高赤惊,不能延時(shí)加載。并且可以天然的防止反射和反序列化漏洞凰锡!)

如何選用?
1未舟、單例對象占用資源 少,不需要延時(shí)加載:枚舉式 好于 餓漢式
2掂为、單例對象 占用 資源 大裕膀,需要延時(shí)加載:靜態(tài)內(nèi)部類式 好于 懶漢式

四、各種方式的效率(相對)

        long start = System.currentTimeMillis();

        int ThreadNum = 10;
        final CountDownLatch countDownLatch = new CountDownLatch(ThreadNum);

        for(int i=0;i<ThreadNum;i++){
            new Thread(new Runnable(){
                @Override
                public void run() {
                    for(int i=0;i<1000000;i++){
                        //Object o = SingletonDome04.getSingleton();
                        Object o = SingletonDome05.SINGLETON;
                    }
                    countDownLatch.countDown();
                }
            }).start();
        }

        countDownLatch.await();//main線程阻塞勇哗,直到計(jì)數(shù)器變?yōu)?昼扛,才會(huì)繼續(xù)往下執(zhí)行

        long end = System.currentTimeMillis();
        System.out.println("耗時(shí):"+(end - start));

餓漢式SingletonDome01 : 86
懶漢式SingletonDome02 : 374
雙重檢測鎖SingletonDome03 : 143
靜態(tài)內(nèi)部類SingletonDome03 : 474
枚舉SingletonDome03 : 24

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市欲诺,隨后出現(xiàn)的幾起案子抄谐,更是在濱河造成了極大的恐慌,老刑警劉巖瞧栗,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件斯稳,死亡現(xiàn)場離奇詭異,居然都是意外死亡迹恐,警方通過查閱死者的電腦和手機(jī)挣惰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人憎茂,你說我怎么就攤上這事珍语。” “怎么了竖幔?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵板乙,是天一觀的道長。 經(jīng)常有香客問我拳氢,道長募逞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任馋评,我火速辦了婚禮放接,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘留特。我一直安慰自己纠脾,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布蜕青。 她就那樣靜靜地躺著苟蹈,像睡著了一般。 火紅的嫁衣襯著肌膚如雪右核。 梳的紋絲不亂的頭發(fā)上慧脱,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天,我揣著相機(jī)與錄音贺喝,去河邊找鬼磷瘤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛搜变,可吹牛的內(nèi)容都是我干的采缚。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼挠他,長吁一口氣:“原來是場噩夢啊……” “哼扳抽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起殖侵,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤贸呢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后拢军,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體楞陷,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年茉唉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了固蛾。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片结执。...
    茶點(diǎn)故事閱讀 38,646評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖艾凯,靈堂內(nèi)的尸體忽然破棺而出献幔,到底是詐尸還是另有隱情,我是刑警寧澤趾诗,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布蜡感,位于F島的核電站,受9級(jí)特大地震影響恃泪,放射性物質(zhì)發(fā)生泄漏郑兴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一贝乎、第九天 我趴在偏房一處隱蔽的房頂上張望杈笔。 院中可真熱鬧,春花似錦糕非、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至持钉,卻和暖如春衡招,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背每强。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工始腾, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人空执。 一個(gè)月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓浪箭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親辨绊。 傳聞我的和親對象是個(gè)殘疾皇子奶栖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 單例模式(SingletonPattern)一般被認(rèn)為是最簡單、最易理解的設(shè)計(jì)模式门坷,也因?yàn)樗暮啙嵰锥桑琼?xiàng)目中最...
    成熱了閱讀 4,231評論 4 34
  • 1 單例模式的動(dòng)機(jī) 對于一個(gè)軟件系統(tǒng)的某些類而言,我們無須創(chuàng)建多個(gè)實(shí)例默蚌。舉個(gè)大家都熟知的例子——Windows任務(wù)...
    justCode_閱讀 1,432評論 2 9
  • 設(shè)計(jì)模式概述 在學(xué)習(xí)面向?qū)ο笃叽笤O(shè)計(jì)原則時(shí)需要注意以下幾點(diǎn):a) 高內(nèi)聚冻晤、低耦合和單一職能的“沖突”實(shí)際上,這兩者...
    彥幀閱讀 3,736評論 0 14
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,339評論 8 265
  • 聽說你要買房设江?去那里買?買多大的温数?戶型怎么樣的绣硝?什么房子叫好房子?怎么樣“撿漏”撑刺?這些你都知道嗎鹉胖?買房子是很重要的...
    城南少東家閱讀 235評論 0 0