/// <summary>
/// 全局唯一的配置信息
/// </summary>
public class Config
{
private static Config _config = null;
public static Config GetConfig()
{
if (_config == null)
{
_config = new Config();
}
return _config;
}
}
單例模式
所謂單例,就是整個程序有且僅有一個實例盒揉。該類負(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)注點卻無外乎以下幾個:
- private的構(gòu)造函數(shù)乘综,主要是為了避免外部通過New創(chuàng)建實例
- 單例是否支持延遲加載,以及性能是否高效
- 多線程環(huán)境下套硼,對象創(chuàng)建是否安全
- 全局只有一個訪問入口
為了達到以上幾個要求卡辰,我們可以有很多的實現(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)線程安全的單例模式挺尿。
單例模式缺陷
- 面向?qū)ο笤O(shè)計講究封裝,繼承炊邦,多態(tài)编矾,以及抽象。單例模式對于其中的繼承铣耘,多態(tài)支持得不好洽沟,抽象講究的是面向接口編程,單例模式并沒有接口概念蜗细。拿以上配置文件的單例為例裆操,假設(shè)現(xiàn)在的配置信息是以本地文件的方式進行加載,如果后期要加入從數(shù)據(jù)庫加載配置信息這個需求炉媒,單例模式必須修改現(xiàn)有代碼踪区,這在一定程度上就違反了設(shè)計規(guī)則。所以單例在一定程度上丟失了應(yīng)對未來需求的擴張性吊骤。
- 單例模式在職責(zé)上有時候會過重缎岗,即要負(fù)責(zé)初始化的過程,又要負(fù)責(zé)初始化的內(nèi)容白粉,甚至在某些情況下還要負(fù)責(zé)其他程序传泊,這在一定程度上違反了“單一職責(zé)”原則。
- 由于單例模式對外之后一個入口點鸭巴,并沒有顯示的利用構(gòu)造函數(shù)傳參的方式進行初始化眷细,內(nèi)部使用了哪些類型并不能很快識別出來,開發(fā)人員很難識別出類的依賴關(guān)系
- 單例模式并不適合那些表面是單例鹃祖,但是未來還有可能擴展的場景溪椎。舉個栗子:線程池在很多程序中都被設(shè)計成單例模式,很多開發(fā)人員認(rèn)為程序中只存在一個線程池,但是在個別需求下校读,同一個程序需要多個線程池的場景是存在的沼侣。
寫在最后
單例模式最為常用的一種模式,有其自己的優(yōu)勢和適用場景歉秫。如果一個類型在程序中要求實例化的數(shù)量有要求的蛾洛,該怎么辦呢?比如端考,一個類型可以最多實例化10個雅潭,或者每個線程可以實例化一個,你可能需要研究一下threadLocal 或者h(yuǎn)ashmap等知識了却特。至于集群間的單例實現(xiàn)歡迎大家在留言區(qū)體現(xiàn)7龉!
更多精彩文章