Java——單例模式

學(xué)習(xí)資料:

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

最近項(xiàng)目中經(jīng)常用到單例模式,雖然能手寫出來,但了解的東西并不多,并不確定為何要這樣寫,以及這樣寫的好處,書上正好看到,就學(xué)習(xí)了解


1. 單例模式

總體來說設(shè)計(jì)模式分為三大類:<p>
創(chuàng)建型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。<p>
結(jié)構(gòu)型模式,共七種:適配器模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。<p>
行為型模式,共十一種:策略模式、模板方法模式、觀察者模式、迭代子模式、責(zé)任鏈模式、命令模式、備忘錄模式、狀態(tài)模式、訪問者模式、中介者模式、解釋器模式

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


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

適用場(chǎng)景:

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

好處:

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

1.1 簡(jiǎn)單實(shí)例

單例模式的核心在于通過一個(gè)方法返回唯一的對(duì)象實(shí)例

代碼

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

    private static Singleton instance = new Singleton();

    public Singleton getInstance() {
        return instance;
    }
}

代碼的核心在于:

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

這種寫法簡(jiǎn)單可靠,缺點(diǎn)就是 無法對(duì)instance實(shí)例進(jìn)行延遲加載


1.1.1 缺點(diǎn)

當(dāng)采用簡(jiǎn)單實(shí)例形式時(shí),若此時(shí)的單例類還扮演著其他的角色,由于instance成員是static的,當(dāng)JVM加載單例類時(shí),單例對(duì)象就會(huì)被創(chuàng)建,導(dǎo)致在任何時(shí)候想要試用這個(gè)單例類,都會(huì)進(jìn)行初始化這個(gè)單例變量

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

    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

當(dāng)使用Singleton.createString()方法時(shí),總會(huì)先創(chuàng)建出一個(gè)Singleton對(duì)象實(shí)例,這就是所謂的不滿足實(shí)例延遲加載


1.2 實(shí)例延遲加載

代碼:

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,確保啟動(dòng)時(shí)沒有額外的負(fù)載

其次,在getInstance()方法中,判斷當(dāng)前的單例是否已存在,若存在則返回;不存在則建立單例實(shí)例

需要注意的是在getInstance()前加了synchronized,不加的話當(dāng)在多線程時(shí),線程1正在創(chuàng)建單例過程中,完成賦值前,線程2可能判斷instancenull,線程2就會(huì)啟動(dòng)創(chuàng)建單例的語句,導(dǎo)致多個(gè)實(shí)例被創(chuàng)建

這種延遲加載的缺點(diǎn)在于性能消耗


1.2.1 驗(yàn)證synchronized作用

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

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

        }
        return instance;
    }

測(cè)試:

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同步后可以避免實(shí)例被多次創(chuàng)建

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

private volatile static LazySingleton instance = null

暫時(shí)對(duì)volatile不了解,以后再學(xué)習(xí)


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

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

測(cè)試:

    @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));
    }

當(dāng)使用Singleton.getInstance()平均耗時(shí)25 milliseconds

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

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


1.3 改進(jìn)型延遲加載

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

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

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),先對(duì)instance進(jìn)行判斷,若不為null則不需要進(jìn)行同步鎖操作,從而避免了同步鎖在多線程下帶來的性能消耗,而且由于有同步鎖,也能避免多次創(chuàng)建實(shí)例

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


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;
    }
}

當(dāng)InclassSingletong被加載時(shí),內(nèi)部類并不會(huì)加載,而當(dāng)getInstance()調(diào)用時(shí),才會(huì)初始化instance

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

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


1.4 單例模式的序列化

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

    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;
    }
}

進(jìn)行單元測(cè)試:

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

        String fileName ="Singleton.txt";
        //將實(shí)例串行化到文件
        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();

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

若將readResolve()方法去掉,則會(huì)報(bào)異常,說s1s0指向不同的實(shí)例

需要序列化單例類的場(chǎng)景很少見,這里了解下


2. 最后

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

共勉 :)

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

相關(guān)閱讀更多精彩內(nèi)容

  • 單例模式簡(jiǎn)介 在GoF的23種設(shè)計(jì)模式中,單例模式是比較簡(jiǎn)單的一種。然而,有時(shí)候越是簡(jiǎn)單的東西越容易出現(xiàn)問題。下面...
    王帥199207閱讀 1,545評(píng)論 0 107
  • 1. 實(shí)現(xiàn)單例模式 餓漢模式和懶漢模式單例模式根據(jù)實(shí)例化時(shí)機(jī)分為餓漢模式和懶漢模式。餓漢模式,是指不等到單例真正使...
    aaron1993閱讀 299評(píng)論 0 0
  • 目錄一.什么是單例?二.有幾種?三.應(yīng)用場(chǎng)景四.注意的地方 一.什么是單例? 單例模式 保證一個(gè)類在內(nèi)存中只有一個(gè)...
    在挖坑的猿閱讀 941評(píng)論 0 0
  • 一、前言 作為對(duì)象的創(chuàng)建模式,單例模式確保某一個(gè)類只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。這個(gè)類稱為...
    manimaniho閱讀 499評(píng)論 0 0
  • 近日,泰國一部電影成為一匹黑馬,把作弊拍得這么跌宕起伏確實(shí)少見。相信很多人看后從中想起了自己的青春時(shí)代,傍上一只學(xué)...
    深夜萍公子閱讀 655評(píng)論 0 0

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