6種單例模式

http://rannn.cc/2017/02/10/singleton.html

如何正確地寫出單例模式

單例模式算是設(shè)計(jì)模式中最容易理解癌椿,也是最容易手寫代碼的模式了吧。但是其中的坑卻不少,所以也常作為面試題來考。本文主要對(duì)幾種單例寫法的整理,并分析其優(yōu)缺點(diǎn)休涤。很多都是一些老生常談的問題,但如果你不知道如何創(chuàng)建一個(gè)線程安全的單例笛辟,不知道什么是雙檢鎖滑绒,那這篇文章可能會(huì)幫助到你。

  1. 懶加載 線程不安全
    當(dāng)被問到要實(shí)現(xiàn)一個(gè)單例模式時(shí)隘膘,很多人的第一反應(yīng)是寫出如下的代碼疑故,包括教科書上也是這樣教我們的。
public class Singleton {
    private static Singleton uniqueInstance;
    private Singleton (){}
    public static Singleton getInstance() {
     if (uniqueInstance == null) {
         uniqueInstance = new Singleton();
     }
     return uniqueInstance;
    }
}

這段代碼簡(jiǎn)單明了弯菊,而且使用了懶加載模式纵势,但是卻存在致命的問題。當(dāng)有多個(gè)線程并行調(diào)用 getInstance() 的時(shí)候管钳,就會(huì)創(chuàng)建多個(gè)實(shí)例钦铁。也就是說在多線程下不能正常工作。

  1. 懶加載 線程安全
    為了解決上面的問題才漆,最簡(jiǎn)單的方法是將整個(gè) getInstance() 方法設(shè)為同步(synchronized)牛曹。
public static synchronized Singleton getInstance() {
    if (uniqueInstance == null) {
        uniqueInstance = new Singleton();
    }
    return uniqueInstance;
}

雖然做到了線程安全,并且解決了多實(shí)例的問題醇滥,但是它并不高效黎比。因?yàn)樵谌魏螘r(shí)候只能有一個(gè)線程調(diào)用 getInstance() 方法超营。但是同步操作只需要在第一次調(diào)用時(shí)才被需要,即第一次創(chuàng)建單例實(shí)例對(duì)象時(shí)阅虫。這就引出了雙重檢驗(yàn)鎖演闭。

  1. 雙重檢查加鎖 線程安全
    雙重檢驗(yàn)加鎖模式(double checked locking pattern),是一種使用同步塊加鎖的方法颓帝。程序員稱其為雙重檢查鎖米碰,因?yàn)闀?huì)有兩次檢查 uniqueInstance == null,一次是在同步塊外购城,一次是在同步塊內(nèi)吕座。為什么在同步塊內(nèi)還要再檢驗(yàn)一次?因?yàn)榭赡軙?huì)有多個(gè)線程一起進(jìn)入同步塊外的 if瘪板,如果在同步塊內(nèi)不進(jìn)行二次檢驗(yàn)的話就會(huì)生成多個(gè)實(shí)例了吴趴。
public static Singleton getSingleton() {
    if (uniqueInstance == null) {                         //Single Checked
        synchronized (Singleton.class) {
            if (uniqueInstance == null) {                 //Double Checked
                uniqueInstance = new Singleton();
            }
        }
    }
    return uniqueInstance;
}

這段代碼看起來很完美,很可惜篷帅,它是有問題。主要在于uniqueInstance = new Singleton()這句拴泌,這并非是一個(gè)原子操作魏身,事實(shí)上在 JVM 中這句話大概做了下面 3 件事情。

給 uniqueInstance 分配內(nèi)存

調(diào)用 Singleton 的構(gòu)造函數(shù)來初始化成員變量

將uniqueInstance對(duì)象指向分配的內(nèi)存空間(執(zhí)行完這步 uniqueInstance 就為非 null 了)

但是在 JVM的即時(shí)編譯器中存在指令重排序的優(yōu)化蚪腐。也就是說上面的第二步和第三步的順序是不能保證的箭昵,最終的執(zhí)行順序可能是 1-2-3 也可能是 1-3-2。如果是后者回季,則在 3 執(zhí)行完畢家制、2 未執(zhí)行之前,被線程二搶占了泡一,這時(shí)uniqueInstance已經(jīng)是非 null 了(但卻沒有初始化)颤殴,所以線程二會(huì)直接返回 uniqueInstance,然后使用鼻忠,然后順理成章地報(bào)錯(cuò)涵但。

我們只需要將 uniqueInstance 變量聲明成 volatile 就可以了。

public class Singleton {
    private volatile static Singleton uniqueInstance; //聲明成 volatile
    private Singleton (){}
    public static Singleton getSingleton() {
        if (uniqueInstance == null) {                         
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {       
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

有些人認(rèn)為使用 volatile 的原因是可見性帖蔓,也就是可以保證線程在本地不會(huì)存有 uniqueInstance 的副本矮瘟,每次都是去主內(nèi)存中讀取。但其實(shí)是不對(duì)的塑娇。使用 volatile 的主要原因是其另一個(gè)特性:禁止指令重排序優(yōu)化澈侠。也就是說,在 volatile 變量的賦值操作后面會(huì)有一個(gè)內(nèi)存屏障(生成的匯編代碼上)埋酬,讀操作不會(huì)被重排序到內(nèi)存屏障之前哨啃。比如上面的例子烧栋,取操作必須在執(zhí)行完 1-2-3 之后或者 1-3-2 之后,不存在執(zhí)行到 1-3 然后取到值的情況棘催。從「先行發(fā)生原則」的角度理解的話劲弦,就是對(duì)于一個(gè) volatile 變量的寫操作都先行發(fā)生于后面對(duì)這個(gè)變量的讀操作(這里的“后面”是時(shí)間上的先后順序)。

但是特別注意在 Java 5 以前的版本使用了 volatile 的雙檢鎖還是有問題的醇坝。其原因是 Java 5 以前的 JMM (Java 內(nèi)存模型)是存在缺陷的邑跪,即時(shí)將變量聲明成 volatile 也不能完全避免重排序,主要是 volatile 變量前后的代碼仍然存在重排序問題呼猪。這個(gè) volatile 屏蔽重排序的問題在 Java 5 中才得以修復(fù)画畅,所以在這之后才可以放心使用 volatile。

相信你不會(huì)喜歡這種復(fù)雜又隱含問題的方式宋距,當(dāng)然我們有更好的實(shí)現(xiàn)線程安全的單例模式的辦法轴踱。

  1. 急加載 static final field 線程安全
    這種方法非常簡(jiǎn)單,因?yàn)閱卫膶?shí)例被聲明成 static 和 final 變量了谚赎,在第一次加載類到內(nèi)存中時(shí)就會(huì)初始化淫僻,所以創(chuàng)建實(shí)例本身是線程安全的。
public class Singleton{
    //類加載時(shí)就初始化
    private static final Singleton uniqueInstance = new Singleton();
    private Singleton(){}
    public static Singleton getInstance(){
        return uniqueInstance;
    }
}

這種寫法如果完美的話壶唤,就沒必要在啰嗦那么多雙檢鎖的問題了雳灵。缺點(diǎn)是它不是一種懶加載模式(lazy initialization),單例會(huì)在加載類后一開始就被初始化闸盔,即使客戶端沒有調(diào)用 getInstance()方法悯辙。餓漢式的創(chuàng)建方式在一些場(chǎng)景中將無(wú)法使用:譬如 Singleton 實(shí)例的創(chuàng)建是依賴參數(shù)或者配置文件的,在 getInstance() 之前必須調(diào)用某個(gè)方法設(shè)置參數(shù)給它迎吵,那樣這種單例寫法就無(wú)法使用了躲撰。

5.靜態(tài)內(nèi)部類 static nested class 線程安全
我比較傾向于使用靜態(tài)內(nèi)部類的方法,這種方法也是《Effective Java》上所推薦的击费。

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

這種寫法仍然使用JVM本身機(jī)制保證了線程安全問題拢蛋;由于 SingletonHolder 是私有的,除了 getInstance() 之外沒有辦法訪問它蔫巩,因此它是懶加載的瓤狐;同時(shí)讀取實(shí)例的時(shí)候不會(huì)進(jìn)行同步,沒有性能缺陷批幌;也不依賴 JDK 版本础锐。

  1. 枚舉 Enum 線程安全
    用枚舉寫單例實(shí)在太簡(jiǎn)單了!這也是它最大的優(yōu)點(diǎn)荧缘。下面這段代碼就是聲明枚舉實(shí)例的通常做法皆警。
public enum EasySingleton{
    INSTANCE;
}

我們可以通過EasySingleton.INSTANCE來訪問實(shí)例,這比調(diào)用getInstance()方法簡(jiǎn)單多了截粗。創(chuàng)建枚舉默認(rèn)就是線程安全的信姓,所以不需要擔(dān)心double checked locking鸵隧,而且還能防止反序列化導(dǎo)致重新創(chuàng)建新的對(duì)象。但是還是很少看到有人這樣寫意推,可能是因?yàn)椴惶煜ぐ伞?/p>

總結(jié)

一般來說豆瘫,單例模式有五種寫法:懶加載、急加載菊值、雙重檢查加鎖鎖外驱、靜態(tài)內(nèi)部類、枚舉腻窒。上述所說都是線程安全的實(shí)現(xiàn)昵宇,文章開頭給出的第一種方法不算正確的寫法。

就我個(gè)人而言儿子,一般情況下直接使用急加載就好了瓦哎,如果明確要求要懶加載(lazy initialization)會(huì)傾向于使用靜態(tài)內(nèi)部類,如果涉及到反序列化創(chuàng)建對(duì)象時(shí)會(huì)試著使用枚舉的方式來實(shí)現(xiàn)單例柔逼。

代碼打包

完整代碼 Singleton

Read More

Double Checked Locking on Singleton Class in Java

http://javarevisited.blogspot.sg/2012/07/why-enum-singleton-are-better-in-java.html

How to create thread safe Singleton in Java

10 Singleton Pattern Interview questions in Java

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蒋譬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子愉适,更是在濱河造成了極大的恐慌犯助,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件儡毕,死亡現(xiàn)場(chǎng)離奇詭異也切,居然都是意外死亡扑媚,警方通過查閱死者的電腦和手機(jī)腰湾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來疆股,“玉大人费坊,你說我怎么就攤上這事⊙裕” “怎么了附井?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)两残。 經(jīng)常有香客問我永毅,道長(zhǎng),這世上最難降的妖魔是什么人弓? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任沼死,我火速辦了婚禮,結(jié)果婚禮上崔赌,老公的妹妹穿的比我還像新娘意蛀。我一直安慰自己耸别,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布县钥。 她就那樣靜靜地躺著秀姐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪若贮。 梳的紋絲不亂的頭發(fā)上省有,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音兜看,去河邊找鬼锥咸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛细移,可吹牛的內(nèi)容都是我干的搏予。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼弧轧,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼雪侥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起精绎,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤速缨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后代乃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體旬牲,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年搁吓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了原茅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡堕仔,死狀恐怖擂橘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情摩骨,我是刑警寧澤通贞,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站恼五,受9級(jí)特大地震影響昌罩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜灾馒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一茎用、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦绘搞、人聲如沸彤避。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)琉预。三九已至,卻和暖如春蒿褂,著一層夾襖步出監(jiān)牢的瞬間圆米,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工啄栓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留娄帖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓昙楚,卻偏偏與公主長(zhǎng)得像近速,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子堪旧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

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

  • 一削葱、單例概念 單例模式是一種對(duì)象創(chuàng)建模式,它用于產(chǎn)生一個(gè)對(duì)象的具體實(shí)例淳梦,它可以確保系統(tǒng)中一個(gè)類只產(chǎn)生一個(gè)實(shí)例 二析砸、...
    圈子里的圈子閱讀 214評(píng)論 0 0
  • 前言 本文主要參考 那些年,我們一起寫過的“單例模式”爆袍。 何為單例模式首繁? 顧名思義,單例模式就是保證一個(gè)類僅有一個(gè)...
    tandeneck閱讀 2,488評(píng)論 1 8
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法陨囊,類相關(guān)的語(yǔ)法弦疮,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法谆扎,異常的語(yǔ)法挂捅,線程的語(yǔ)...
    子非魚_t_閱讀 31,598評(píng)論 18 399
  • 從三月份找實(shí)習(xí)到現(xiàn)在芹助,面了一些公司堂湖,掛了不少,但最終還是拿到小米状土、百度无蜂、阿里、京東蒙谓、新浪斥季、CVTE、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,209評(píng)論 11 349
  • 署光初照大廣場(chǎng), 藍(lán)天白云人向往酣倾。 欣逢盛世練太極舵揭, 神犬止步欲觀賞。
    笑對(duì)人生起伏閱讀 131評(píng)論 0 0