5分鐘理解設(shè)計模式 —— 單例模式

單例.png

概述:

5分鐘理解設(shè)計模式系列,將通過解決實際問題,來帶您理解設(shè)計模式乌询,本文希望帶您搞懂的3個問題是:

  1. 為什么使用單例模式?
    2.你有哪些實現(xiàn)單例模式的方法豌研?
    3.單例模式是[金手指]嗎妹田?

1 為什么使用單例模式?

單例模式(Singleton Design Pattern)鹃共,一個類只允許創(chuàng)建一個對象(或者實例)鬼佣,這個類就是一個單例類,這種設(shè)計模式霜浴,就叫做單例模式晶衷。
  單例模式主要用于:處理資源訪問沖突表示全局唯一

處理資源訪問

例如:我們有一個Logger類阴孟,通過FileWriter類來記錄日志晌纫,記錄日志的文件位置是:info.log,我們在程序中可能會有很多地方需要記錄日志永丝,那么如果我們每一次記錄日志锹漱,都創(chuàng)建一個Logger類的話,那么每一個Logger類都會去寫info.log文件(每個Logger對應(yīng)一個FileWriter慕嚷,多個FileWriter同時寫入)哥牍,此時info.log就是一個競爭資源,兩個線程同時寫入數(shù)據(jù)喝检,就可能出現(xiàn)數(shù)據(jù)覆蓋的情況嗅辣。但是如果Logger類是一個單例類,那么由于FileWriter是一個線程安全的對象挠说,那么就不會出現(xiàn)數(shù)據(jù)覆蓋的問題澡谭。

表示全局唯一

在我們的程序開發(fā)過程中钙态,有一些類需要被表示成全局唯一嘉冒,比如我們的配置文件在系統(tǒng)中只有一份,那么當配置文件被加載到內(nèi)存后蔫耽,以對象的形式存在撩炊,也應(yīng)該存一份外永,再比如全局的id自增生成器、線程池拧咳、對象池等對象伯顶,也可以設(shè)計成單例。

2 你有哪些實現(xiàn)單例模式的方法骆膝?

1 餓漢式:

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

在類加載的時候祭衩,創(chuàng)建對象,有人覺得這種實現(xiàn)方式不好阅签,因為不支持延遲加載掐暮。
  但是我并不認同這種說法,因為如果該對象加載耗時非常長政钟,那么最好不要等到真正去使用它的時候路克,再去初始化樟结,因為這樣會影響性能,甚至會由于接口響應(yīng)時間過長導(dǎo)致超時失敗精算。如果該實力占用資源較多瓢宦,可能會引起程序報錯(比如 Java 中的 PermGen Space OOM),那么在程序初始化時拋出異常灰羽,我們還可以進行調(diào)整驮履,但是如果這個異常是在程序運行時拋出的,那么會導(dǎo)致整個程序崩潰廉嚼,所以有問題應(yīng)該提早暴露玫镐,遵循fail-fast的設(shè)計原則

2 雙重檢測懶漢式

public class Singleton {
    private Singleton() {
    }
    private static Singleton singleton = null;
    public Singleton getInstance(){
        // 提高性能,降低線程進入臨界區(qū)的可能
        if(singleton == null){
            synchronized (Singleton.class){
                if(singleton == null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

這也是單例模式比較經(jīng)典的寫法怠噪,網(wǎng)上有人說由于指令重排恐似,需要在加volatile關(guān)鍵字,禁止指令重排舰绘。實際上這個問題在高版本的jdk中已經(jīng)修復(fù)了蹂喻,修復(fù)方法是將對象的new操作改成原子操作。低版本的指令重排問題產(chǎn)生的原因我也寫在了文章的最后捂寿,感興趣的同學(xué)也可以了解一下口四。

3 靜態(tài)內(nèi)部類

這是一種比雙重檢測的單例模式更簡單的寫法,也可以做到延遲加載:

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

該方法使用的是靜態(tài)內(nèi)部類的特性秦陋,外部類被加載的時候蔓彩,不會創(chuàng)建Inner實例對象,只有調(diào)用 getInstance方法的時候驳概,才會創(chuàng)建Sigleton對象赤嚼,創(chuàng)過程中的線程安全,Singleton的唯一性均由JVM保證

4 枚舉

枚舉是一種最簡單的實現(xiàn)方式顺又,通過java枚舉類的特性更卒,保證唯一性。

public enum Singleton  {
    INSTANCE 
    //doSomething 該實例支持的行為
    //可以省略此方法稚照,通過Singleton.INSTANCE進行操作
    public static Singleton get Instance() {
        return Singleton.INSTANCE;
    }
}

3 單例模式是[金手指]嗎蹂空?

并不是,單例模式也存在著一些問題
對OOP的特性不友好(不基于接口果录、對繼承上枕、多態(tài)并不友好)
不支持有參數(shù)的構(gòu)造函數(shù)
對程序的可測試性不友好
對程序的擴展性不友好
如果單例模式的成員變量是全局變量,一但被修改將影響其他調(diào)用類
所以我們可以用過工廠模式弱恒、或者是ioc容器來代替單例模式辨萍,當然使用那種對象創(chuàng)建方法,還應(yīng)該根據(jù)具體的業(yè)務(wù)而定返弹。

選讀:低版本的jdk為什么需要使用到volatile關(guān)鍵字修飾锈玉?

public class Singleton {
    private Singleton() {
    }
    private volatile Singleton singleton = null;
    public Singleton getInstance(){
        // 提高性能爪飘,降低線程進入臨界區(qū)的可能
        if(singleton == null){
            synchronized (Singleton.class){
                if(singleton == null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

代碼中有兩處判空:
  第一處判空,是為了提高性能嘲玫,降低線程進入臨界區(qū)的可能性悦施。
  第二處判空是為了線程同步,假如沒有第二處判空去团,則可能兩個線程都通過了if(singleton==null)條件,這樣即使是臨界區(qū)內(nèi)只有一個線程在執(zhí)行穷蛹,臨界區(qū)內(nèi)的代碼也會被執(zhí)行兩遍土陪,這樣就會產(chǎn)生兩個對象,不符合單例模式肴熏。
  成員變量使用了volatile進行修飾鬼雀,一方面是保證了對象在多線程環(huán)境下的可見性,另一方面是為了防止new Singleton()進行指令重排序而導(dǎo)致的并發(fā)問題蛙吏。
  volatile關(guān)鍵字的作用兩個:
  1 保證變量在線程之間的可見性(直接從主存中讀寫數(shù)據(jù)源哩,不經(jīng)過工作內(nèi)存)
  2 阻止編譯時和運行時的指令重排,編譯時JVM編譯器遵循內(nèi)存屏障約束鸦做,運行時依賴CPU屏障來阻止指令重排励烦。
  指令重排是指JVM在編譯Java代碼的時候,或者CPU在執(zhí)行JVM字節(jié)碼的時候泼诱,對現(xiàn)有的指令順序進行重新排序坛掠。
  指令重排的目的是為了在不改變程序執(zhí)行結(jié)果的前提下,優(yōu)化程序的運行效率治筒。需要注意的是屉栓,這里所說的不改變執(zhí)行結(jié)果,指的是不改變單線程下的程序執(zhí)行結(jié)果耸袜。
  這里不太好懂友多,舉一個例子,正常的new Singleton()創(chuàng)建步驟是:
  1 開辟一塊內(nèi)存空間
  2 創(chuàng)建對象
  3 將對象的地址存入引用變量
  經(jīng)過指令重排后堤框,可能變成了:
  1 開辟一塊內(nèi)存空間
  2 將對象的地址存入引用變量
  3 創(chuàng)建對象
  假設(shè)發(fā)生了指令重排域滥,線程A、B都執(zhí)行這段代碼胰锌,線程A執(zhí)行到了new Singleton()的步驟2骗绕,此時還沒有創(chuàng)建對象,這個時候發(fā)生了線程的切換资昧。線程B開始執(zhí)行酬土,這個時候線程B還可以通過if(singleton == null)的判斷,因為線程A中的singleton只是指向了一個空的內(nèi)存地址格带,這個時候線程B創(chuàng)建出了一個Singleton對象撤缴,當線程切換成A時刹枉,線程A仍執(zhí)行了new Singleton()的步驟3,此時創(chuàng)建了2個Singleton對象屈呕,不符合單例模式微宝。

最后,期待您的訂閱和點贊虎眨,專欄每周都會更新蟋软,希望可以和您一起進步,同時也期待您的批評與指正嗽桩!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末岳守,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子碌冶,更是在濱河造成了極大的恐慌湿痢,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扑庞,死亡現(xiàn)場離奇詭異譬重,居然都是意外死亡,警方通過查閱死者的電腦和手機罐氨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門臀规,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人岂昭,你說我怎么就攤上這事以现。” “怎么了约啊?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵邑遏,是天一觀的道長。 經(jīng)常有香客問我恰矩,道長记盒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任外傅,我火速辦了婚禮纪吮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘萎胰。我一直安慰自己碾盟,他們只是感情好,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布技竟。 她就那樣靜靜地躺著冰肴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上熙尉,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天联逻,我揣著相機與錄音,去河邊找鬼检痰。 笑死包归,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的铅歼。 我是一名探鬼主播公壤,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼椎椰!你這毒婦竟也來了境钟?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤俭识,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后洞渔,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體套媚,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年磁椒,在試婚紗的時候發(fā)現(xiàn)自己被綠了堤瘤。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡浆熔,死狀恐怖本辐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情医增,我是刑警寧澤慎皱,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站叶骨,受9級特大地震影響茫多,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜忽刽,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一天揖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧跪帝,春花似錦今膊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春赖钞,著一層夾襖步出監(jiān)牢的瞬間腰素,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工雪营, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留弓千,地道東北人。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓献起,卻偏偏與公主長得像洋访,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子谴餐,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354

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