單例模式漫談

單例模式

單例模式屬于創(chuàng)建型模式桨武,是Gangs of Four Design patterns中其中的一種肋拔。單例模式的實(shí)現(xiàn)有多種方式,很多實(shí)現(xiàn)方式在開(kāi)發(fā)者中也是比較有爭(zhēng)議的呀酸。本文就單例模式的應(yīng)用場(chǎng)景凉蜂,實(shí)現(xiàn)方式,最佳實(shí)踐做一些淺顯的討論性誉。

java singleton

單例模式確保在虛擬機(jī)中只實(shí)例化一個(gè)對(duì)象窿吩,該singleton class要提供一個(gè)對(duì)此實(shí)例的全局訪問(wèn)點(diǎn)。單例模式一般用來(lái)實(shí)現(xiàn)日志错览,緩存纫雁,線程池等。

單例模式uml類(lèi)圖

java singleton patten

java單例模式的實(shí)現(xiàn)由多種方式倾哺,但是所有的實(shí)現(xiàn)方式都遵循以下幾個(gè)概念

  • 私有的構(gòu)造函數(shù)轧邪,限制其它類(lèi)對(duì)其實(shí)例化
  • 該單例類(lèi)私有的全局靜態(tài)變量
  • 訪問(wèn)該單例的共有靜態(tài)方法

下面我們來(lái)討論單例模式的幾種實(shí)現(xiàn)方式以及實(shí)現(xiàn)中的一些設(shè)計(jì)理念

  • 饑餓式

    public class EagerSingleton {
        private static EagerSingleton INSTANCE = new EagerSingleton();
    
        private EagerSingleton() {
        }
    
        public static EagerSingleton getInstance(){
            return INSTANCE;
        }
    }
    

    饑餓式是在類(lèi)加載的時(shí)候就實(shí)例化了,這樣就有一個(gè)弊端悼粮,就是有時(shí)候我們并沒(méi)有使用該實(shí)例闲勺,但是它卻存在虛擬機(jī)中,如果該實(shí)例引用的資源較少則沒(méi)什么關(guān)系扣猫,反之則顯得比較浪費(fèi)。

  • 懶漢式
public class LazySingleton {
    private static LazySingleton INSTANCE;

    private LazySingleton(){}

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

懶漢式在我們需要使用實(shí)例的時(shí)候它才初始化翘地,這樣就沒(méi)有饑餓式和靜態(tài)塊方式的弊端申尤,但是會(huì)有線程安全的問(wèn)題。

?

  • 靜態(tài)塊單例模式實(shí)現(xiàn)方式
public class StaticBlockSingleton {

    private static  StaticBlockSingleton INSTANCE;

    static {
        try {
            INSTANCE = new StaticBlockSingleton();
        } catch (Exception e) {
            throw new RuntimeException("Exception occured in creating singleton instance");
        }
    }

    private StaticBlockSingleton(){}

    public static StaticBlockSingleton getInstance(){
        return INSTANCE;
    }
}

靜態(tài)塊方式和饑餓式比較類(lèi)似

  • 線程安全的單例模式
public class SynchronizedSingleton {

    private static SynchronizedSingleton INSTANCE;

    private SynchronizedSingleton(){}

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

在訪問(wèn)該實(shí)例的靜態(tài)方法前加上synchronized關(guān)鍵字就可以保證線程安全衙耕,但是這樣的方式會(huì)讓計(jì)算機(jī)付出一些額外的代價(jià)昧穿,從而影響了程序性能。為了盡量在不影響性能的情況下實(shí)現(xiàn)線程安全的單例模式橙喘,double checked locking方式就出現(xiàn)了时鸵,在這種方式下,synchronize塊在方法內(nèi)的if條件中使用,且使用了倆個(gè)if條件檢查實(shí)例是否為null饰潜,確保只有一個(gè)實(shí)例被創(chuàng)建初坠。代碼如下

public class DoubleCheckSingleton {
    private static DoubleCheckSingleton INSTANCE;

    private DoubleCheckSingleton(){}

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

?

  • Bill Pugh的單例模式實(shí)現(xiàn)方式

    在java 5以前,java的內(nèi)存模型在上述方式實(shí)現(xiàn)單例模式的時(shí)候存在許多問(wèn)題彭雾,當(dāng)多個(gè)線程同時(shí)獲取實(shí)例的時(shí)候可能會(huì)失敗碟刺。所以Bill Pugh提出了一種使用內(nèi)部靜態(tài)幫助類(lèi)的方式來(lái)實(shí)現(xiàn)單例模式。

    public class BillPughSingleton{
        private BillPughSingleton(){}
      
          private static class SingletonHelper{
            private static final BillPughSingleton INSTANCE = new BillPughSingleton();
        }
      
          public static BillPughSingleton getInstance(){
            return SingletonHelper.INSTANCE;
        }
    }
    

    值得注意的是該單例模式的實(shí)例放在私有的內(nèi)部靜態(tài)類(lèi)中薯酝,當(dāng)外部類(lèi)加載到內(nèi)存中的時(shí)候半沽,內(nèi)部的幫助內(nèi)并沒(méi)有加載到內(nèi)存,直到’getInstance()‘方法被調(diào)用吴菠,內(nèi)部幫助類(lèi)才會(huì)加載到內(nèi)存中并創(chuàng)建實(shí)例者填。

    這種方式有很多好處,其保證了線程安全做葵,且并沒(méi)有使用synchronized關(guān)鍵字占哟,所以也就不存在額外開(kāi)銷(xiāo)的問(wèn)題。

  • Reflection對(duì)單例模式的破壞

反射能破壞以上所有的實(shí)現(xiàn)方式蜂挪,其能確保獲取到不同的實(shí)例重挑。測(cè)試代碼如下

public class ReflectionSingletonTest {
    public static void main(String[] args) {
        EagerSingleton eagerSingleton = EagerSingleton.getInstance();
        System.out.println(eagerSingleton.hashCode());


        Constructor [] constructors = EagerSingleton.class.getDeclaredConstructors();
        Arrays.asList(constructors).forEach(constructor -> {
            constructor.setAccessible(true);
            try {
                EagerSingleton anotherEagerSingleton = (EagerSingleton) constructor.newInstance();
                System.out.println(anotherEagerSingleton.hashCode());
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        });

    }
}

運(yùn)行結(jié)果如下

356573597
1747585824
  • enum實(shí)現(xiàn)單例模式

    為了解決反射場(chǎng)景下的單例模式,Joshua Bloch建議使用enum來(lái)實(shí)現(xiàn)單例模式棠涮,因?yàn)閑num在java中被確保只實(shí)例化一次谬哀。

    public enum EnumSinglton {
        INSTANCE;
    
        public void dosomething(){
            System.out.println("enum singleton");
        }
    }
    

    ?

  • 序列化和單例

    在分布式系統(tǒng)中,我們需要在單例類(lèi)中實(shí)現(xiàn)序列化接口严肪,這樣我們就能保存實(shí)例的狀態(tài)史煎,并在合適的時(shí)候根據(jù)狀態(tài)恢復(fù)實(shí)例。

    public class SerializedSingleton implements Serializable{
    
        private SerializedSingleton(){
        }
    
        private static class SerializedSingletonHelper{
            public static final SerializedSingleton INSTANCE = new SerializedSingleton();
        }
    
        public static SerializedSingleton getInstance(){
            return SerializedSingletonHelper.INSTANCE;
        }
    }
    

    測(cè)試代碼

    public class SerializedSingletonTest {
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.ser"));
            oos.writeObject(SerializedSingleton.getInstance());
            System.out.println(SerializedSingleton.getInstance().hashCode());
    
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.ser"));
            SerializedSingleton anotherSingleton = (SerializedSingleton) ois.readObject();
            System.out.println(anotherSingleton.hashCode());
        }
    }
    

    測(cè)試結(jié)果

    325040804
    999966131
    
    Process finished with exit code 0
    

    以上測(cè)試結(jié)果證明了序列化違反了單例模式的原則驳糯,要解決這個(gè)問(wèn)題我們需要在單例類(lèi)中提供一個(gè)readResolve()方法的實(shí)現(xiàn)

    protected Object readResolve() {
        return getInstance();
    }
    

    這樣當(dāng)JVM從內(nèi)存中反序列化地"組裝"一個(gè)新對(duì)象時(shí),就會(huì)自動(dòng)調(diào)用這個(gè) readResolve方法來(lái)返回我們指定好的對(duì)象了, 單例規(guī)則也就得到了保證.

    我們?cè)谶\(yùn)行測(cè)試代碼 測(cè)試結(jié)果如下

    325040804
    325040804
    

    ?

單例模式線程安全的分析與測(cè)試

android中單例模式引起的內(nèi)存泄漏

Java Singleton Design Pattern Best Practices with Examples

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末篇梭,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子酝枢,更是在濱河造成了極大的恐慌恬偷,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帘睦,死亡現(xiàn)場(chǎng)離奇詭異袍患,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)竣付,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)诡延,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人古胆,你說(shuō)我怎么就攤上這事肆良。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵惹恃,是天一觀的道長(zhǎng)夭谤。 經(jīng)常有香客問(wèn)我,道長(zhǎng)座舍,這世上最難降的妖魔是什么沮翔? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮曲秉,結(jié)果婚禮上采蚀,老公的妹妹穿的比我還像新娘。我一直安慰自己承二,他們只是感情好榆鼠,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著亥鸠,像睡著了一般妆够。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上负蚊,一...
    開(kāi)封第一講書(shū)人閱讀 51,718評(píng)論 1 305
  • 那天神妹,我揣著相機(jī)與錄音,去河邊找鬼家妆。 笑死鸵荠,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的伤极。 我是一名探鬼主播蛹找,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼哨坪!你這毒婦竟也來(lái)了庸疾?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤当编,失蹤者是張志新(化名)和其女友劉穎届慈,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體忿偷,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拧篮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了牵舱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缺虐,死狀恐怖芜壁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤慧妄,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布顷牌,位于F島的核電站,受9級(jí)特大地震影響塞淹,放射性物質(zhì)發(fā)生泄漏窟蓝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一饱普、第九天 我趴在偏房一處隱蔽的房頂上張望运挫。 院中可真熱鬧,春花似錦套耕、人聲如沸谁帕。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)匈挖。三九已至,卻和暖如春康愤,著一層夾襖步出監(jiān)牢的瞬間儡循,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工征冷, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留择膝,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓资盅,卻偏偏與公主長(zhǎng)得像调榄,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子呵扛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

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

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理每庆,服務(wù)發(fā)現(xiàn),斷路器今穿,智...
    卡卡羅2017閱讀 134,665評(píng)論 18 139
  • 1 場(chǎng)景問(wèn)題# 1.1 讀取配置文件的內(nèi)容## 考慮這樣一個(gè)應(yīng)用缤灵,讀取配置文件的內(nèi)容。 很多應(yīng)用項(xiàng)目蓝晒,都有與應(yīng)用相...
    七寸知架構(gòu)閱讀 6,773評(píng)論 12 68
  • 前言 本文主要參考 那些年腮出,我們一起寫(xiě)過(guò)的“單例模式”。 何為單例模式芝薇? 顧名思義胚嘲,單例模式就是保證一個(gè)類(lèi)僅有一個(gè)...
    tandeneck閱讀 2,515評(píng)論 1 8
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類(lèi)相關(guān)的語(yǔ)法洛二,內(nèi)部類(lèi)的語(yǔ)法馋劈,繼承相關(guān)的語(yǔ)法攻锰,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 31,643評(píng)論 18 399
  • 1 單例模式的動(dòng)機(jī) 對(duì)于一個(gè)軟件系統(tǒng)的某些類(lèi)而言妓雾,我們無(wú)須創(chuàng)建多個(gè)實(shí)例娶吞。舉個(gè)大家都熟知的例子——Windows任務(wù)...
    justCode_閱讀 1,433評(píng)論 2 9