我眼中的單例模式并不完美

image
/// <summary>
    /// 全局唯一的配置信息
    /// </summary>
   public class Config
    {
        private static Config _config = null;
        public static Config GetConfig()
        {
            if (_config == null)
            {
                _config = new Config();
            }
            return _config;
        }
    }
image

單例模式

所謂單例,就是整個程序有且僅有一個實例盒揉。該類負(fù)責(zé)創(chuàng)建自己的對象预烙,同時確保只有一個對象被創(chuàng)建。

幾乎大部分程序員面試的時候翘县,面試官讓你說出三種常用的設(shè)計模式谴分,單例必是其中之一牺蹄。平時所說的單例模式是指一個進程內(nèi)只存在某個類型的一個實例,其實擴展到集群這個概念氓奈,位于不同物理環(huán)境的多個進程之間也可以有單例這種概念鼎天,像平時吹水用的分布式鎖其實可以看做是多進程之間的單例模式。

透過單例的現(xiàn)象可以看到單例模式本質(zhì)上也是解決資源競爭的問題育勺,它讓多個線程甚至多個進程共享同一個資源涧至,以達到資源共享的目的桑包。為什么要實現(xiàn)資源共享呢?因為這個資源在業(yè)務(wù)場景下只能存在一個實例蓖康,例如以上的全局配置信息蒜焊,如果程序內(nèi)部有多個配置信息實例倒信,不僅浪費了服務(wù)器的內(nèi)存資源,在配置信息發(fā)生修改的時候泳梆,多個實例隨之同步更新又是一個很大的問題鳖悠。

單例實現(xiàn)

單例模式的概念很簡單,實現(xiàn)的方式也有很多优妙,但是關(guān)注點卻無外乎以下幾個:

  1. private的構(gòu)造函數(shù)乘综,主要是為了避免外部通過New創(chuàng)建實例
  2. 單例是否支持延遲加載,以及性能是否高效
  3. 多線程環(huán)境下套硼,對象創(chuàng)建是否安全
  4. 全局只有一個訪問入口

為了達到以上幾個要求卡辰,我們可以有很多的實現(xiàn)方法

餓漢式
public class Config
    {
        private Config() { }
        private static Config _config = new Config ();
        public static Config GetConfig()
        {
           
            return _config;
        }
    }

這種方式主要是利用了語言特性,一個類型的靜態(tài)屬性是屬于類型所有邪意,在類的生命周期內(nèi)只會加載一次,所以以上代碼實現(xiàn)單例模式并沒有問題雾鬼,而且超級簡單萌朱。

很多人說這種方式不妥,在類型的加載時候就完成實例創(chuàng)建策菜,沒有達到惰性加載晶疼,會造成內(nèi)存的浪費。至于這個問題我并不表示完全贊同又憨。如果一個單例的初始化耗時比較長翠霍,最好不要等到真正用它的時候才去執(zhí)行初始化,這樣會影響系統(tǒng)的性能竟块。餓漢式可以實現(xiàn)在程序啟動的時候就進行初始化操作壶运,這樣就能避免初始化時間過長導(dǎo)致的性能問題,而且還有一個比較重要的好處浪秘,如果初始化程序有錯誤,我們可以在程序啟動的時候就發(fā)現(xiàn)埠况,而不用等到程序上線運行時才暴露出來耸携。這就好比編譯期錯誤永遠(yuǎn)比運行時錯誤好排查的道理類似。

懶漢式

程序員妹子貢獻的代碼其實就屬于懶漢式辕翰,表面上看可以實現(xiàn)惰性加載夺衍,但是在多線程的環(huán)境下,會產(chǎn)生多個實例喜命,問題就在于 if (_config == null) 這個語句并非是線程安全的沟沙。如果非要改造的話河劝,可以加上全局的鎖機制,有一個注意點矛紫,這里鎖的對象一定要是一個static全局的對象

 private static object objLock = new object();
        private static Config _config = null;
        public static Config GetConfig()
        {
            lock (objLock)
            {
                if (_config == null)
                {
                    _config = new Config();
                }
            }           
            return _config;
        }
雙重加鎖機制

雖然懶漢式方式能保證線程安全赎瞎,但是鎖的機制缺大大降低了系統(tǒng)性能,原因是鎖機制把所有請求順序化了颊咬,為了改善懶漢式的性能务甥,所以雙重加鎖機制出現(xiàn)了,在保證了線程安全的情況下喳篇,大大提高了程序性能

private static object objLock = new object();
        private static Config _config = null;
        public static Config GetConfig()
        {
            if (_config == null)
            {
                lock (objLock)
                {
                    if (_config == null)
                    {
                        _config = new Config();
                    }
                }
            }
                
            return _config;
        }

以上只是實現(xiàn)單例的幾種常用方式敞临,根據(jù)每個語言的特性還有很多可以實現(xiàn)單例的方式,比如:利用c#或者java的內(nèi)部類麸澜,靜態(tài)初始化特性等都可以實現(xiàn)線程安全的單例模式挺尿。

image

單例模式缺陷

  1. 面向?qū)ο笤O(shè)計講究封裝,繼承炊邦,多態(tài)编矾,以及抽象。單例模式對于其中的繼承铣耘,多態(tài)支持得不好洽沟,抽象講究的是面向接口編程,單例模式并沒有接口概念蜗细。拿以上配置文件的單例為例裆操,假設(shè)現(xiàn)在的配置信息是以本地文件的方式進行加載,如果后期要加入從數(shù)據(jù)庫加載配置信息這個需求炉媒,單例模式必須修改現(xiàn)有代碼踪区,這在一定程度上就違反了設(shè)計規(guī)則。所以單例在一定程度上丟失了應(yīng)對未來需求的擴張性吊骤。
  2. 單例模式在職責(zé)上有時候會過重缎岗,即要負(fù)責(zé)初始化的過程,又要負(fù)責(zé)初始化的內(nèi)容白粉,甚至在某些情況下還要負(fù)責(zé)其他程序传泊,這在一定程度上違反了“單一職責(zé)”原則。
  3. 由于單例模式對外之后一個入口點鸭巴,并沒有顯示的利用構(gòu)造函數(shù)傳參的方式進行初始化眷细,內(nèi)部使用了哪些類型并不能很快識別出來,開發(fā)人員很難識別出類的依賴關(guān)系
  4. 單例模式并不適合那些表面是單例鹃祖,但是未來還有可能擴展的場景溪椎。舉個栗子:線程池在很多程序中都被設(shè)計成單例模式,很多開發(fā)人員認(rèn)為程序中只存在一個線程池,但是在個別需求下校读,同一個程序需要多個線程池的場景是存在的沼侣。

寫在最后

單例模式最為常用的一種模式,有其自己的優(yōu)勢和適用場景歉秫。如果一個類型在程序中要求實例化的數(shù)量有要求的蛾洛,該怎么辦呢?比如端考,一個類型可以最多實例化10個雅潭,或者每個線程可以實例化一個,你可能需要研究一下threadLocal 或者h(yuǎn)ashmap等知識了却特。至于集群間的單例實現(xiàn)歡迎大家在留言區(qū)體現(xiàn)7龉!

更多精彩文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末裂明,一起剝皮案震驚了整個濱河市椿浓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌闽晦,老刑警劉巖扳碍,帶你破解...
    沈念sama閱讀 211,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異仙蛉,居然都是意外死亡笋敞,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評論 3 385
  • 文/潘曉璐 我一進店門荠瘪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來夯巷,“玉大人,你說我怎么就攤上這事哀墓〕貌停” “怎么了?”我有些...
    開封第一講書人閱讀 157,435評論 0 348
  • 文/不壞的土叔 我叫張陵篮绰,是天一觀的道長后雷。 經(jīng)常有香客問我,道長吠各,這世上最難降的妖魔是什么臀突? 我笑而不...
    開封第一講書人閱讀 56,509評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮贾漏,結(jié)果婚禮上惧辈,老公的妹妹穿的比我還像新娘。我一直安慰自己磕瓷,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,611評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著困食,像睡著了一般边翁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上硕盹,一...
    開封第一講書人閱讀 49,837評論 1 290
  • 那天符匾,我揣著相機與錄音,去河邊找鬼瘩例。 笑死啊胶,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的垛贤。 我是一名探鬼主播焰坪,決...
    沈念sama閱讀 38,987評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼聘惦!你這毒婦竟也來了某饰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,730評論 0 267
  • 序言:老撾萬榮一對情侶失蹤善绎,失蹤者是張志新(化名)和其女友劉穎黔漂,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體禀酱,經(jīng)...
    沈念sama閱讀 44,194評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡炬守,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,525評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了剂跟。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片减途。...
    茶點故事閱讀 38,664評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖浩聋,靈堂內(nèi)的尸體忽然破棺而出观蜗,到底是詐尸還是另有隱情,我是刑警寧澤衣洁,帶...
    沈念sama閱讀 34,334評論 4 330
  • 正文 年R本政府宣布墓捻,位于F島的核電站,受9級特大地震影響坊夫,放射性物質(zhì)發(fā)生泄漏砖第。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,944評論 3 313
  • 文/蒙蒙 一环凿、第九天 我趴在偏房一處隱蔽的房頂上張望梧兼。 院中可真熱鬧,春花似錦智听、人聲如沸羽杰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽考赛。三九已至惕澎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間颜骤,已是汗流浹背唧喉。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留忍抽,地道東北人八孝。 一個月前我還...
    沈念sama閱讀 46,389評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像鸠项,于是被迫代替她去往敵國和親干跛。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,554評論 2 349

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