Java——單例模式

學習資料:

《Java程序性能優(yōu)化》秩冈,這本書蠻不錯的,豆瓣評分挺高7.9。本篇就是第2章第一章節(jié)的讀書筆記

最近項目中經(jīng)常用到單例模式咖祭,雖然能手寫出來,但了解的東西并不多蔫骂,并不確定為何要這樣寫么翰,以及這樣寫的好處,書上正好看到辽旋,就學習了解


1. 單例模式

總體來說設(shè)計模式分為三大類:<p>
創(chuàng)建型模式浩嫌,共五種:工廠方法模式、抽象工廠模式补胚、單例模式码耐、建造者模式、原型模式溶其。<p>
結(jié)構(gòu)型模式骚腥,共七種:適配器模式、裝飾器模式瓶逃、代理模式束铭、外觀模式、橋接模式厢绝、組合模式契沫、享元模式。<p>
行為型模式昔汉,共十一種:策略模式懈万、模板方法模式、觀察者模式、迭代子模式会通、責任鏈模式口予、命令模式、備忘錄模式涕侈、狀態(tài)模式苹威、訪問者模式、中介者模式驾凶、解釋器模式

摘自Java 的 23 種設(shè)計模式全解析


單例模式是一種對象創(chuàng)建模式, 可以用來確保一個類只產(chǎn)生一個對象的具體實例掷酗。

適用場景:

  • 系統(tǒng)的關(guān)鍵組件
  • 被頻繁使用的對象

好處:

  1. 對于頻繁使用的對象调违,可以省略創(chuàng)建對象所花費的時間。尤其是一些重量級的對象泻轰,可以省下一些系統(tǒng)開銷
  2. 由于new次數(shù)減少技肩,對系統(tǒng)內(nèi)存的使用頻率也會降低,從而減輕GC壓力浮声,縮短GC停頓時間

1.1 簡單實例

單例模式的核心在于通過一個方法返回唯一的對象實例

代碼

public class Singleton {
    private Singleton() {
        System.out.println("Singleton is created");// 創(chuàng)建單例的過程可能會比較慢
    }

    private static Singleton instance = new Singleton();

    public Singleton getInstance() {
        return instance;
    }
}

代碼的核心在于:

一個private訪問級的構(gòu)造函數(shù)虚婿;private staticinstance成員變量;public staticgetInstance()方法

這種寫法簡單可靠泳挥,缺點就是 無法對instance實例進行延遲加載


1.1.1 缺點

當采用簡單實例形式時然痊,若此時的單例類還扮演著其他的角色,由于instance成員是static的屉符,當JVM加載單例類時剧浸,單例對象就會被創(chuàng)建,導致在任何時候想要試用這個單例類矗钟,都會進行初始化這個單例變量

public class Singleton {
    private Singleton() {
        System.out.println("Singleton is created");// 創(chuàng)建單例的過程可能會比較慢
    }

    private static Singleton instance = new Singleton();

    public static Singleton getInstance() {
        return instance;
    }

    public static void createString(){ //模擬單例類扮演其他角色
        System.out.println("CreateString in Singleton");
    }
}

使用Singleton.createString()唆香,輸出結(jié)果:

Singleton is created
CreateString in Singleton

當使用Singleton.createString()方法時,總會先創(chuàng)建出一個Singleton對象實例吨艇,這就是所謂的不滿足實例延遲加載


1.2 實例延遲加載

代碼:

public class LazySingleton {
    private LazySingleton() {
        System.out.println("LazySingleton is created");
    }

    private static LazySingleton instance = null;

    public static synchronized LazySingleton getInstance() {
        if (null == instance) {
             instance = new LazySingleton();
        }
        return instance;
    }
}

首先躬它,將靜態(tài)成員變量的初始值賦予null,確保啟動時沒有額外的負載

其次东涡,在getInstance()方法中冯吓,判斷當前的單例是否已存在,若存在則返回软啼;不存在則建立單例實例

需要注意的是在getInstance()前加了synchronized桑谍,不加的話當在多線程時,線程1正在創(chuàng)建單例過程中祸挪,完成賦值前锣披,線程2可能判斷instancenull,線程2就會啟動創(chuàng)建單例的語句,導致多個實例被創(chuàng)建

這種延遲加載的缺點在于性能消耗


1.2.1 驗證synchronized作用

getInstance()前的synchronized去掉雹仿,修改代碼

    public static  LazySingleton getInstance() {
        if (null == instance) {
            try {
                TimeUnit.MILLISECONDS.sleep(10);//模擬耗時操作增热,線程sleep
                instance = new LazySingleton();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        return instance;
    }

測試:

public class MyThread extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            LazySingleton instance = LazySingleton.getInstance();
            System.out.println(instance);
        }
    }

    public static void main(String[] args) {
        MyThread[] myThreads = new MyThread[5];
        for (int i = 0; i < 5; i++) {
            myThreads[i] = new MyThread();
        }

        beginTime = System.currentTimeMillis();
        for (MyThread myThread : myThreads) {
            myThread.start();
        }
    }
}

部分輸出結(jié)果:

LazySingleton is created
l.single.LazySingleton@71053f05
l.single.LazySingleton@71053f05
l.single.LazySingleton@71053f05
LazySingleton is created
l.single.LazySingleton@6c749686
l.single.LazySingleton@6c749686
l.single.LazySingleton@6c749686
LazySingleton is created
l.single.LazySingleton@3a9ea3d2
l.single.LazySingleton@3a9ea3d2
l.single.LazySingleton@3a9ea3d2

由結(jié)果看出,LazySingleton被多次創(chuàng)建胧辽,說明synchronized同步后可以避免實例被多次創(chuàng)建

在其他的博客看到峻仇,還用volatile來修飾instance

private volatile static LazySingleton instance = null

暫時對volatile不了解,以后再學習


1.2.2 延遲加載帶來的性能消耗

使用了synchronizedLazySingleton在多線程下會有更多的性能消耗

測試:

    @Override
    public void run() {
        long  beginTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
//            Singleton.getInstance();
            LazySingleton.getInstance();
        }
        System.out.println("spend: " + (System.currentTimeMillis() - beginTime));
    }

當使用Singleton.getInstance()平均耗時25 milliseconds

使用LazySingleton.getInstance()平均耗時200 milliseconds

實際耗時需要根據(jù)自己電腦來確定


1.3 改進型延遲加載

上面的LazySingleton雖然支持了延遲加載并防止了多次創(chuàng)建實例邑商,卻導致了額外的性能消耗摄咆,推薦兩種改進形式:雙檢查鎖與內(nèi)部類

兩種方式在使用時根據(jù)場景選擇,若需要通過構(gòu)造方法傳遞參數(shù)人断,則選擇雙檢查鎖形式吭从;若不需要則都可以

1.3.1 雙檢查鎖形式

public class LazySingleton {
    private LazySingleton() {
        System.out.println("LazySingleton is created");
    }

    private static volatile LazySingleton instance = null;

    
    public static LazySingleton getInstance() {
        if (null == instance) {
            synchronized (LazySingleton.class) {
                if (null == instance) {
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}

getInstance()方法內(nèi),先對instance進行判斷恶迈,若不為null則不需要進行同步鎖操作涩金,從而避免了同步鎖在多線程下帶來的性能消耗,而且由于有同步鎖暇仲,也能避免多次創(chuàng)建實例

經(jīng)測試步做,這種方式使用1.2.2的方式測試,平均耗時15 milliseconds
經(jīng)1.2.1的方式測試奈附,也沒有出現(xiàn)多處創(chuàng)建實例


1.3.1 內(nèi)部類方式

代碼:

public class InclassSingleton {
    private InclassSingleton() {
        System.out.println("InclassSingleton is created");
    }

    private static class SingletonHolder {
        private static InclassSingleton instance = new InclassSingleton();
    }

    public static InclassSingleton getInstance() {
        return SingletonHolder.instance;
    }
}

InclassSingletong被加載時全度,內(nèi)部類并不會加載,而當getInstance()調(diào)用時斥滤,才會初始化instance

由于實例的建立是在類加載時完成讼载,天生多線程友好,所有不需要同步synchronized關(guān)鍵字

這種兩種形式基本可以保證不會創(chuàng)建多個實例中跌,只有極特殊的情景咨堤,例如通過反射強行調(diào)用單例類的私有構(gòu)造函數(shù)會造成多次創(chuàng)建實例


1.4 單例模式的序列化

public class Singleton implements Serializable {
    private Singleton() {
        System.out.println("Singleton is created");// 創(chuàng)建單例的過程可能會比較慢
    }

    private static Singleton instance = new Singleton();

    public static Singleton getInstance() {
        return instance;
    }

    public static void createString() { //模擬單例類扮演其他角色
        System.out.println("CreateString in Singleton");
    }


    protected Object readResolve()  {
        return instance;
    }
}

進行單元測試:

    @Test
    public void test() throws Exception {
        Singleton s1 = null;
        Singleton s0 = Singleton.getInstance();

        String fileName ="Singleton.txt";
        //將實例串行化到文件
        FileOutputStream fos = new FileOutputStream(fileName);
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(s0);
        oos.flush();
        oos.close();

        //從文件讀出所有的單例類
        FileInputStream fis = new FileInputStream(fileName);
        ObjectInputStream ois = new ObjectInputStream(fis);
        s1 = (Singleton) ois.readObject();

        //進行比較
        assertEquals(s0,s1);
    }

若將readResolve()方法去掉,則會報異常漩符,說s1s0指向不同的實例

需要序列化單例類的場景很少見一喘,這里了解下


2. 最后

學習了解常見的設(shè)計模式,以后閱讀一些庫的源碼或者自己寫代碼時也有些幫助

共勉 :)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嗜暴,一起剝皮案震驚了整個濱河市凸克,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌闷沥,老刑警劉巖萎战,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異舆逃,居然都是意外死亡蚂维,警方通過查閱死者的電腦和手機戳粒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來虫啥,“玉大人,你說我怎么就攤上這事苹祟∑来疲” “怎么了景东?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長丝里。 經(jīng)常有香客問我杯聚,道長幌绍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮伪冰,結(jié)果婚禮上贮聂,老公的妹妹穿的比我還像新娘吓懈。我一直安慰自己,他們只是感情好隔嫡,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布蕾各。 她就那樣靜靜地躺著庆揪,像睡著了一般缸榛。 火紅的嫁衣襯著肌膚如雪内颗。 梳的紋絲不亂的頭發(fā)上均澳,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天找前,我揣著相機與錄音躺盛,去河邊找鬼槽惫。 笑死,一個胖子當著我的面吹牛仿耽,可吹牛的內(nèi)容都是我干的氓仲。 我是一名探鬼主播得糜,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼啥箭,長吁一口氣:“原來是場噩夢啊……” “哼急侥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起贝润,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎尊蚁,沒想到半個月后侣夷,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體百拓,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡衙传,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了腺阳。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片亭引。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡焙蚓,死狀恐怖购公,靈堂內(nèi)的尸體忽然破棺而出宏浩,到底是詐尸還是另有隱情比庄,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站净神,受9級特大地震影響强挫,放射性物質(zhì)發(fā)生泄漏俯渤。R本人自食惡果不足惜八匠,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望抡四。 院中可真熱鬧仗谆,春花似錦隶垮、人聲如沸狸吞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽京郑。三九已至些举,卻和暖如春户魏,著一層夾襖步出監(jiān)牢的瞬間叼丑,已是汗流浹背纵寝。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留室奏,地道東北人胧沫。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像窖逗,于是被迫代替她去往敵國和親佑附。 傳聞我的和親對象是個殘疾皇子音同,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

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

  • 單例模式簡介 在GoF的23種設(shè)計模式中,單例模式是比較簡單的一種必搞。然而恕洲,有時候越是簡單的東西越容易出現(xiàn)問題霜第。下面...
    王帥199207閱讀 1,501評論 0 107
  • 1. 實現(xiàn)單例模式 餓漢模式和懶漢模式單例模式根據(jù)實例化時機分為餓漢模式和懶漢模式咆课。餓漢模式喇澡,是指不等到單例真正使...
    aaron1993閱讀 219評論 0 0
  • 目錄一.什么是單例?二.有幾種?三.應用場景四.注意的地方 一.什么是單例秀睛? 單例模式 保證一個類在內(nèi)存中只有一個...
    在挖坑的猿閱讀 841評論 0 0
  • 一锐帜、前言 作為對象的創(chuàng)建模式缴阎,單例模式確保某一個類只有一個實例述暂,而且自行實例化并向整個系統(tǒng)提供這個實例视卢。這個類稱為...
    manimaniho閱讀 436評論 0 0
  • 近日绳锅,泰國一部電影成為一匹黑馬,把作弊拍得這么跌宕起伏確實少見酝掩。相信很多人看后從中想起了自己的青春時代鳞芙,傍上一只學...
    深夜萍公子閱讀 597評論 0 0