創(chuàng)建型:單例模式

為什么使用單例模式?

  • 盡可能的節(jié)約內(nèi)存空間邦邦,減少無謂的GC消耗

  • 這些類安吁,在應用中如果有兩個或者兩個以上的實例會引起錯誤,又或者我換句話說燃辖,就是這些類鬼店,在整個應用中,同一時刻郭赐,有且只能有一種狀態(tài)。eg: 一般實踐當中确沸,有很多應用級別的資源會被做成單例捌锭,比如配置文件信息,邏輯上來講罗捎,整個應用有且只能在同在時間有一個观谦,當然如果你有多個,這可能并不會引起程序級別錯誤桨菜,這里指的錯誤特指異郴碜矗或者ERROR。但是當我們試圖改變配置文件的時候倒得,問題就出來了泻红。

在我的工作過程中,我發(fā)現(xiàn)所有可以使用單例模式的類都有一個共性霞掺,那就是這個類沒有自己的狀態(tài)谊路,換句話說,這些類無論你實例化多少個菩彬,其實都是一樣的缠劝,而且更重要的一點是潮梯,這個類如果有兩個或者兩個以上的實例的話,我的程序竟然會產(chǎn)生程序錯誤或者與現(xiàn)實相違背的邏輯錯誤惨恭。

這樣的話秉馏,如果我們不將這個類控制成單例的結(jié)構(gòu),應用中就會存在很多一模一樣的類實例脱羡,這會非常浪費系統(tǒng)的內(nèi)存資源萝究,而且容易導致錯誤甚至一定會產(chǎn)生錯誤,所以我們單例模式所期待的目標或者說使用它的目的轻黑,是為了盡可能的節(jié)約內(nèi)存空間糊肤,減少無謂的GC消耗,并且使應用可以正常運作氓鄙。

寫法:

1. 最傻的寫法:

public class SingleInstance{
      private static SingleInstance singleton;

      private SingleInstance(){
              //doInit
      }

      public static SingleInstance getInstance(){
              if(singleton == null){
                      singleton = new SingleInstance();
              }
      }
}

這種就不多說了馆揉,在多個線程執(zhí)行下可能會創(chuàng)建出很多個實例不能保證單例。

2. 加上鎖的第一種寫法:

public class BadSynchronizedSingleton {

    //一個靜態(tài)的實例
    private static BadSynchronizedSingleton synchronizedSingleton;
    //私有化構(gòu)造函數(shù)
    private BadSynchronizedSingleton(){}
    //給出一個公共的靜態(tài)方法返回一個單一實例
    public synchronized static BadSynchronizedSingleton getInstance(){
        if (synchronizedSingleton == null) {
            synchronizedSingleton = new BadSynchronizedSingleton();
        }
        return synchronizedSingleton;
    }
    
}

這種做法把整個方法鎖住確實是能解決上面說的問題抖拦,但是凡是一個線程使用getInstance就會把這個對象鎖住升酣。造成很多無謂的等待。

3. 單例模式版本态罪,也稱為雙重加鎖:

public class SynchronizedSingleton {

    //一個靜態(tài)的實例
    private static SynchronizedSingleton synchronizedSingleton;
    //私有化構(gòu)造函數(shù)
    private SynchronizedSingleton(){}
    //給出一個公共的靜態(tài)方法返回一個單一實例
    public static SynchronizedSingleton getInstance(){
        if (synchronizedSingleton == null) {
            synchronized (SynchronizedSingleton.class) {
                if (synchronizedSingleton == null) {
                    synchronizedSingleton = new SynchronizedSingleton();
                }
            }
        }
        return synchronizedSingleton;
    }
}

這種做法與上面那種最無腦的同步做法相比就要好很多了噩茄,因為我們只是在當前實例為null,也就是實例還未創(chuàng)建時才進行同步复颈,否則就直接返回绩聘,這樣就節(jié)省了很多無謂的線程等待時間,值得注意的是在同步塊中耗啦,我們再次判斷了synchronizedSingleton是否為null凿菩,解釋下為什么要這樣做。

假設(shè)我們?nèi)サ敉綁K中的是否為null的判斷帜讲,有這樣一種情況衅谷,假設(shè)A線程和B線程都在同步塊外面判斷了synchronizedSingleton為null,結(jié)果A線程首先獲得了線程鎖似将,進入了同步塊获黔,然后A線程會創(chuàng)造一個實例,此時synchronizedSingleton已經(jīng)被賦予了實例在验,A線程退出同步塊玷氏,直接返回了第一個創(chuàng)造的實例,此時B線程獲得線程鎖腋舌,也進入同步塊预茄,此時A線程其實已經(jīng)創(chuàng)造好了實例,B線程正常情況應該直接返回的,但是因為同步塊里沒有判斷是否為null耻陕,直接就是一條創(chuàng)建實例的語句拙徽,所以B線程也會創(chuàng)造一個實例返回,此時就造成創(chuàng)造了多個實例的情況诗宣。

經(jīng)過剛才的分析膘怕,貌似上述雙重加鎖的示例看起來是沒有問題了,但如果再進一步深入考慮的話召庞,其實仍然是有問題的岛心。

如果我們深入到JVM中去探索上面這段代碼,它就有可能(注意篮灼,只是有可能)是有問題的忘古。

因為虛擬機在執(zhí)行創(chuàng)建實例的這一步操作的時候,其實是分了好幾步去進行的诅诱,也就是說創(chuàng)建一個新的對象并非是原子性操作髓堪。在有些JVM中上述做法是沒有問題的,但是有些情況下是會造成莫名的錯誤娘荡。

首先要明白在JVM創(chuàng)建新的對象時干旁,主要要經(jīng)過三步。

  1. 分配內(nèi)存

  2. 初始化構(gòu)造器

  3. 將對象指向分配的內(nèi)存的地址

這種順序在上述雙重加鎖的方式是沒有問題的炮沐,因為這種情況下JVM是完成了整個對象的構(gòu)造才將內(nèi)存的地址交給了對象争群。但是如果2和3步驟是相反的(2和3可能是相反的是因為JVM會針對字節(jié)碼進行調(diào)優(yōu),而其中的一項調(diào)優(yōu)便是調(diào)整指令的執(zhí)行順序)大年,就會出現(xiàn)問題了换薄。

因為這時將會先將內(nèi)存地址賦給對象,針對上述的雙重加鎖翔试,就是說先將分配好的內(nèi)存地址指給synchronizedSingleton轻要,然后再進行初始化構(gòu)造器,這時候后面的線程去請求getInstance方法時遏餐,會認為synchronizedSingleton對象已經(jīng)實例化了伦腐,直接返回一個引用赢底。如果在初始化構(gòu)造器之前失都,這個線程使用了synchronizedSingleton,就會產(chǎn)生莫名的錯誤幸冻。

所以我們在語言級別無法完全避免錯誤的發(fā)生粹庞,我們只有將該任務交給JVM,所以有一種比較標準的單例模式洽损。如下所示庞溜。

package com.oneinstance;

public class InnerClassSingleton {
    
    public static Singleton getInstance(){
        return Singleton.singleton;
    }

    private static class Singleton{
        
        protected static Singleton singleton = new Singleton();
        
    }
}

咱們上面說的都是懶漢式加載的方式,還有一種餓漢式加載的方式:

public class Singleton {
    
    private static Singleton singleton = new Singleton();
    
    private Singleton(){}
    
    public static Singleton getInstance(){
        return singleton;
    }
    
}

從始至終就沒有使用這個實例,造成內(nèi)存的浪費流码。
不過在有些時候又官,直接初始化單例的實例也無傷大雅,對項目幾乎沒什么影響漫试,比如我們在應用啟動時就需要加載的配置文件等六敬,就可以采取這種方式去保證單例。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末驾荣,一起剝皮案震驚了整個濱河市外构,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌播掷,老刑警劉巖审编,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異歧匈,居然都是意外死亡垒酬,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進店門眯亦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伤溉,“玉大人,你說我怎么就攤上這事妻率÷夜耍” “怎么了?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵宫静,是天一觀的道長走净。 經(jīng)常有香客問我,道長孤里,這世上最難降的妖魔是什么伏伯? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮捌袜,結(jié)果婚禮上卸勺,老公的妹妹穿的比我還像新娘刃滓。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布苹享。 她就那樣靜靜地躺著公罕,像睡著了一般身堡。 火紅的嫁衣襯著肌膚如雪们童。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天敦跌,我揣著相機與錄音澄干,去河邊找鬼。 笑死,一個胖子當著我的面吹牛麸俘,可吹牛的內(nèi)容都是我干的辩稽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼从媚,長吁一口氣:“原來是場噩夢啊……” “哼搂誉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起静檬,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤炭懊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后拂檩,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體侮腹,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年稻励,在試婚紗的時候發(fā)現(xiàn)自己被綠了父阻。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡望抽,死狀恐怖加矛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情煤篙,我是刑警寧澤斟览,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站辑奈,受9級特大地震影響苛茂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鸠窗,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一妓羊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧稍计,春花似錦躁绸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至茧球,卻和暖如春庭瑰,著一層夾襖步出監(jiān)牢的瞬間星持,已是汗流浹背抢埋。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人揪垄。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓穷吮,卻偏偏與公主長得像,于是被迫代替她去往敵國和親饥努。 傳聞我的和親對象是個殘疾皇子捡鱼,可洞房花燭夜當晚...
    茶點故事閱讀 45,066評論 2 355

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