單例模式:深入理解單例模式——只有一個實(shí)例

目錄:

文章目錄

前言

一 單例模式簡介

1.1 定義

1.2 為什么要用單例模式呢?

1.3 為什么不使用全局變量確保一個類只有一個實(shí)例呢反砌?

二 單例的模式的實(shí)現(xiàn)

2.1 餓漢方式(線程安全)

2.2 懶漢式(非線程安全和synchronized關(guān)鍵字線程安全版本 )

2.3 懶漢式(雙重檢查加鎖版本)

2.4 懶漢式(登記式/靜態(tài)內(nèi)部類方式)

2.5 餓漢式(枚舉方式)

2.6 總結(jié)

前言

初遇設(shè)計模式在上個寒假猎拨,當(dāng)時把每個設(shè)計模式過了一遍膀藐,對設(shè)計模式有了一個最初級的了解屠阻。這個學(xué)期借了幾本設(shè)計模式的書籍看,聽了老師的設(shè)計模式課额各,對設(shè)計模式算是有個更進(jìn)一步的認(rèn)識国觉。后面可能會不定期更新一下自己對于設(shè)計模式的理解。每個設(shè)計模式看似很簡單臊泰,實(shí)則想要在一個完整的系統(tǒng)中應(yīng)用還是非常非常難的蛉加。然后我的水品也非常非常有限,代碼量也不是很多缸逃,只能通過閱讀書籍、思考別人的編碼經(jīng)驗(yàn)以及結(jié)合自己的編碼過程中遇到的問題來總結(jié)厂抽。

怎么用->怎么用才好->怎么與其他模式結(jié)合使用需频,我想這是每個開發(fā)人員都需要逾越的一道鴻溝。

一 單例模式簡介

1.1 定義

保證一個類僅有一個實(shí)例筷凤,并提供一個訪問它的全局訪問點(diǎn)昭殉。

1.2 為什么要用單例模式呢?

在我們的系統(tǒng)中藐守,有一些對象其實(shí)我們只需要一個挪丢,比如說:線程池、緩存卢厂、對話框乾蓬、注冊表、日志對象慎恒、充當(dāng)打印機(jī)任内、顯卡等設(shè)備驅(qū)動程序的對象。事實(shí)上融柬,這一類對象只能有一個實(shí)例死嗦,如果制造出多個實(shí)例就可能會導(dǎo)致一些問題的產(chǎn)生,比如:程序的行為異常粒氧、資源使用過量越除、或者不一致性的結(jié)果。

簡單來說使用單例模式可以帶來下面幾個好處:

對于頻繁使用的對象外盯,可以省略創(chuàng)建對象所花費(fèi)的時間摘盆,這對于那些重量級對象而言,是非趁殴郑可觀的一筆系統(tǒng)開銷骡澈;
由于 new 操作的次數(shù)減少,因而對系統(tǒng)內(nèi)存的使用頻率也會降低掷空,這將減輕 GC 壓力肋殴,縮短 GC 停頓時間囤锉。

1.3 為什么不使用全局變量確保一個類只有一個實(shí)例呢?

我們知道全局變量分為靜態(tài)變量和實(shí)例變量护锤,靜態(tài)變量也可以保證該類的實(shí)例只存在一個官地。
只要程序加載了類的字節(jié)碼,不用創(chuàng)建任何實(shí)例對象烙懦,靜態(tài)變量就會被分配空間驱入,靜態(tài)變量就可以被使用了。

但是氯析,如果說這個對象非常消耗資源亏较,而且程序某次的執(zhí)行中一直沒用,這樣就造成了資源的浪費(fèi)掩缓。利用單例模式的話雪情,我們就可以實(shí)現(xiàn)在需要使用時才創(chuàng)建對象,這樣就避免了不必要的資源浪費(fèi)你辣。 不僅僅是因?yàn)檫@個原因巡通,在程序中我們要盡量避免全局變量的使用,大量使用全局變量給程序的調(diào)試舍哄、維護(hù)等帶來困難宴凉。

二 單例的模式的實(shí)現(xiàn)

通常單例模式在Java語言中,有兩種構(gòu)建方式:

餓漢方式表悬。指全局的單例實(shí)例在類裝載時構(gòu)建
懶漢方式弥锄。指全局的單例實(shí)例在第一次被使用時構(gòu)建。
不管是那種創(chuàng)建方式签孔,它們通常都存在下面幾點(diǎn)相似處:

單例類必須要有一個 private 訪問級別的構(gòu)造函數(shù)叉讥,只有這樣,才能確保單例不會在系統(tǒng)中的其他代碼內(nèi)被實(shí)例化;
instance 成員變量和 uniqueInstance 方法必須是 static 的饥追。

2.1 餓漢方式(線程安全)

public class Singleton {
   //在靜態(tài)初始化器中創(chuàng)建單例實(shí)例图仓,這段代碼保證了線程安全
    private static Singleton uniqueInstance = new Singleton();
    //Singleton類只有一個構(gòu)造方法并且是被private修飾的,所以用戶無法通過new方法創(chuàng)建該對象實(shí)例
    private Singleton(){}
    public static Singleton getInstance(){
        return uniqueInstance;
    }
}

所謂 “餓漢方式” 就是說JVM在加載這個類時就馬上創(chuàng)建此唯一的單例實(shí)例但绕,不管你用不用救崔,先創(chuàng)建了再說,如果一直沒有被使用捏顺,便浪費(fèi)了空間六孵,典型的空間換時間,每次調(diào)用的時候幅骄,就不需要再判斷劫窒,節(jié)省了運(yùn)行時間。

2.2 懶漢式(非線程安全和synchronized關(guān)鍵字線程安全版本 )

public class Singleton {  
  private static Singleton uniqueInstance;  
  private Singleton (){
  }   
  //沒有加入synchronized關(guān)鍵字的版本是線程不安全的
  public static Singleton getInstance() {
  //判斷當(dāng)前單例是否已經(jīng)存在拆座,若存在則返回主巍,不存在則再建立單例
      if (uniqueInstance == null) {  
      uniqueInstance = new Singleton();  
      }  
      return uniqueInstance;  
  }  
 }

所謂 “ 懶漢式” 就是說單例實(shí)例在第一次被使用時構(gòu)建冠息,而不是在JVM在加載這個類時就馬上創(chuàng)建此唯一的單例實(shí)例。

但是上面這種方式很明顯是線程不安全的孕索,如果多個線程同時訪問getInstance()方法時就會出現(xiàn)問題逛艰。如果想要保證線程安全,一種比較常見的方式就是在getInstance() 方法前加上synchronized關(guān)鍵字搞旭,如下:

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

我們知道synchronized關(guān)鍵字偏重量級鎖散怖。雖然在JavaSE1.6之后synchronized關(guān)鍵字進(jìn)行了主要包括:為了減少獲得鎖和釋放鎖帶來的性能消耗而引入的偏向鎖和輕量級鎖以及其它各種優(yōu)化之后執(zhí)行效率有了顯著提升。

但是在程序中每次使用getInstance() 都要經(jīng)過synchronized加鎖這一層肄渗,這難免會增加getInstance()的方法的時間消費(fèi)镇眷,而且還可能會發(fā)生阻塞。我們下面介紹到的 雙重檢查加鎖版本 就是為了解決這個問題而存在的翎嫡。

2.3 懶漢式(雙重檢查加鎖版本)

利用雙重檢查加鎖(double-checked locking)偏灿,首先檢查是否實(shí)例已經(jīng)創(chuàng)建,如果尚未創(chuàng)建钝的,“才”進(jìn)行同步。這樣以來铆遭,只有一次同步硝桩,這正是我們想要的效果。

public class Singleton {

//volatile保證枚荣,當(dāng)uniqueInstance變量被初始化成Singleton實(shí)例時碗脊,多個線程可以正確處理uniqueInstance變量
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getInstance() {
   //檢查實(shí)例,如果不存在橄妆,就進(jìn)入同步代碼塊
if (uniqueInstance == null) {
//只有第一次才徹底執(zhí)行這里的代碼
synchronized(Singleton.class) {
   //進(jìn)入同步代碼塊后衙伶,再檢查一次,如果仍是null害碾,才創(chuàng)建實(shí)例
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}

很明顯矢劲,這種方式相比于使用synchronized關(guān)鍵字的方法,可以大大減少getInstance() 的時間消費(fèi)慌随。

我們上面使用到了volatile關(guān)鍵字來保證數(shù)據(jù)的可見性芬沉,關(guān)于volatile關(guān)鍵字的內(nèi)容可以看我的這篇文章:
《Java多線程學(xué)習(xí)(三)volatile關(guān)鍵字》: https://blog.csdn.net/qq_34337272/article/details/79680771

注意: 雙重檢查加鎖版本不適用于1.4及更早版本的Java。
1.4及更早版本的Java中阁猜,許多JVM對于volatile關(guān)鍵字的實(shí)現(xiàn)會導(dǎo)致雙重檢查加鎖的失效丸逸。

2.4 懶漢式(登記式/靜態(tài)內(nèi)部類方式)

靜態(tài)內(nèi)部實(shí)現(xiàn)的單例是懶加載的且線程安全。

只有通過顯式調(diào)用 getInstance 方法時剃袍,才會顯式裝載 SingletonHolder 類黄刚,從而實(shí)例化 instance(只有第一次使用這個單例的實(shí)例的時候才加載,同時不會有線程安全問題)民效。

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

2.5 餓漢式(枚舉方式)

這種實(shí)現(xiàn)方式還沒有被廣泛采用憔维,但這是實(shí)現(xiàn)單例模式的最佳方法涛救。 它更簡潔,自動支持序列化機(jī)制埋同,絕對防止多次實(shí)例化 (如果單例類實(shí)現(xiàn)了Serializable接口州叠,默認(rèn)情況下每次反序列化總會創(chuàng)建一個新的實(shí)例對象,關(guān)于單例與序列化的問題可以查看這一篇文章《單例與序列化的那些事兒》)凶赁,同時這種方式也是《Effective Java 》以及《Java與模式》的作者推薦的方式咧栗。

public enum Singleton {
     //定義一個枚舉的元素,它就是 Singleton 的一個實(shí)例
INSTANCE;  

public void doSomeThing() {  
     System.out.println("枚舉方法實(shí)現(xiàn)單例");
}  
}

使用方法:

public class ESTest {

    public static void main(String[] args) {
        Singleton singleton = Singleton.INSTANCE;
        singleton.doSomeThing();//output:枚舉方法實(shí)現(xiàn)單例

    }

}

《Effective Java 中文版 第二版》

這種方法在功能上與公有域方法相近虱肄,但是它更加簡潔致板,無償提供了序列化機(jī)制,絕對防止多次實(shí)例化咏窿,即使是在面對復(fù)雜序列化或者反射攻擊的時候斟或。雖然這種方法還沒有廣泛采用,但是單元素的枚舉類型已經(jīng)成為實(shí)現(xiàn)Singleton的最佳方法集嵌。 —-《Effective Java 中文版 第二版》

《Java與模式》

《Java與模式》中萝挤,作者這樣寫道,使用枚舉來實(shí)現(xiàn)單實(shí)例控制會更加簡潔根欧,而且無償?shù)靥峁┝诵蛄谢瘷C(jī)制怜珍,并由JVM從根本上提供保障,絕對防止多次實(shí)例化凤粗,是更簡潔酥泛、高效、安全的實(shí)現(xiàn)單例的方式嫌拣。

2.6 總結(jié)

我們主要介紹到了以下幾種方式實(shí)現(xiàn)單例模式:

  • 餓漢方式(線程安全)
  • 懶漢式(非線程安全和synchronized關(guān)鍵字線程安全版本)
  • 懶漢式(雙重檢查加鎖版本)
  • 懶漢式(登記式/靜態(tài)內(nèi)部類方式)
  • 餓漢式(枚舉方式)
    參考:

《Head First 設(shè)計模式》

《Effective Java 中文版 第二版》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末柔袁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子异逐,更是在濱河造成了極大的恐慌捶索,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件应役,死亡現(xiàn)場離奇詭異情组,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)箩祥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門院崇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人袍祖,你說我怎么就攤上這事底瓣。” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵捐凭,是天一觀的道長拨扶。 經(jīng)常有香客問我,道長茁肠,這世上最難降的妖魔是什么患民? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮垦梆,結(jié)果婚禮上匹颤,老公的妹妹穿的比我還像新娘。我一直安慰自己托猩,他們只是感情好印蓖,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著京腥,像睡著了一般赦肃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上公浪,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天他宛,我揣著相機(jī)與錄音,去河邊找鬼欠气。 笑死堕汞,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的晃琳。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼琐鲁,長吁一口氣:“原來是場噩夢啊……” “哼卫旱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起围段,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤顾翼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后奈泪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體适贸,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年涝桅,在試婚紗的時候發(fā)現(xiàn)自己被綠了拜姿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡冯遂,死狀恐怖蕊肥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蛤肌,我是刑警寧澤壁却,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布批狱,位于F島的核電站,受9級特大地震影響展东,放射性物質(zhì)發(fā)生泄漏赔硫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一盐肃、第九天 我趴在偏房一處隱蔽的房頂上張望爪膊。 院中可真熱鬧,春花似錦恼蓬、人聲如沸惊完。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽小槐。三九已至,卻和暖如春荷辕,著一層夾襖步出監(jiān)牢的瞬間凿跳,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工疮方, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留控嗜,地道東北人。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓骡显,卻偏偏與公主長得像疆栏,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子惫谤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評論 2 348

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