單例模式簡(jiǎn)單復(fù)雜,線程不安全到安全

點(diǎn)擊上方「藍(lán)字」關(guān)注我們

0x01 :簡(jiǎn)介
說到單例模式,可以說單例模式是最常見,也是最常用的設(shè)計(jì)模式了。Spring的bean默認(rèn)就是單例的。雖然單例模式是最簡(jiǎn)單的設(shè)計(jì)模式,但是在實(shí)現(xiàn)上有多種方式,分別是餓漢式、懶漢式、雙重校驗(yàn)鎖;在線程安全方面有線程不安全的,也有線程相對(duì)安全的??偟膩碚f實(shí)現(xiàn)單例模式有以下一些特點(diǎn):
1、私有的構(gòu)造方法
2、內(nèi)部創(chuàng)建一個(gè)私有成員變量
3、提供一個(gè)公開、靜態(tài)的獲取成員的方法

類圖
0x02:餓漢模式
首先介紹一下最簡(jiǎn)單的單例模式(餓漢模式),這種方式在單例類被加載的時(shí)候?qū)嵗?/span>
public?class?Singleton?{
??????//內(nèi)部創(chuàng)建一個(gè)私有成員變量
??????private?static?Singleton?instance?=?new?Singleton();;
??????private?Singleton()?{
???????????//私有的構(gòu)造方法
??????}
?????public?static?Singleton?getInstance()?{
?????????//提供一個(gè)公開、靜態(tài)的獲取成員的方法
?????????return?instance;
?????}
?}
或者
public?class?Singleton?{
??????//內(nèi)部創(chuàng)建一個(gè)私有成員變量
??????private?static?Singleton?instance;
??????static?{
??????????instance?=?new?Singleton();
??????}
??????private?Singleton()?{
???????????//私有的構(gòu)造方法
??????}
?????public?static?Singleton?getInstance()?{
?????????//提供一個(gè)公開、靜態(tài)的獲取成員的方法
?????????return?instance;
?????}
?}
餓漢模式的缺點(diǎn)在于,如果單例對(duì)象的創(chuàng)建過程比較耗時(shí),那么將會(huì)導(dǎo)致應(yīng)用程序的啟動(dòng)比較慢。
0x03:懶漢式
為了克服餓漢模式的缺點(diǎn),將單例對(duì)象的創(chuàng)建過程延后到第一次使用單例對(duì)象時(shí),這種實(shí)現(xiàn)方式被稱為懶漢模式。
public?class?Singleton?{
?????private?static?Singleton?instance;
??????private?Singleton()?{
??????}
??????public?static?Singleton?getInstance()?{
??????????if?(instance?==?null)?{
??????????????instance?=?new?Singleton();
?????????}
?????????return?instance;
?????}
?}
需要注意的是懶漢式是線程不安全的。假設(shè)在單例類被實(shí)例化之前,有兩個(gè)線程同時(shí)在獲取單例對(duì)象,線程A在執(zhí)行完if (instance == null) 后,線程調(diào)度機(jī)制將 CPU 資源分配給線程B,此時(shí)線程B在執(zhí)行?if (instance == null)時(shí)也發(fā)現(xiàn)單例類還沒有被實(shí)例化,這樣就會(huì)導(dǎo)致單例類被實(shí)例化兩次。為了防止這種情況發(fā)生,需要對(duì) getInstance() 方法同步處理。改進(jìn)后的懶漢模式:
public?class?Singleton?{
??????private?static?Singleton?instance;
??????private?Singleton()?{
??????}
??????//?線程安全的懶漢模式
??????public?synchronized?static?Singleton?getInstance()?{
??????????if?(instance?==?null)?{
?????????????instance?=?new?Singleton();
?????????}
?????????return?instance;
?????}
?}
可以使用ReentrantLock對(duì)象進(jìn)行同步處理。改進(jìn)后的懶漢模式實(shí)現(xiàn)方式,每次獲取單例對(duì)象時(shí)都會(huì)加鎖,在多線程情況下會(huì)造成性能損耗。
0x04:雙重校驗(yàn)鎖(double check)
雙重校驗(yàn)鎖實(shí)現(xiàn)本質(zhì)也是一種懶漢式,相比懶漢式第2種實(shí)現(xiàn)方式將會(huì)有較大的性能提升。
public?class?Singleton?{??
????private?static?Singleton?instance;??
????private?final?static?Object?lock?=?new?Object();??
????private?Singleton()?{??
????}??
????public?static?Singleton?getInstance(){??
????????if?(instance?==?null)?{??
????????????synchronized?(lock?)?{??
????????????????if?(instance?==?null)?{??
????????????????????instance?=?new?Singleton();??
????????????????}??
????????????}??
????????}??
????????return?instance;??
????}??
}
synchronized同步塊括號(hào)中的鎖定對(duì)象是采用的一個(gè)無關(guān)的Object類實(shí)例,而不是采用this,因?yàn)間etInstance是一個(gè)靜態(tài)方法,在它內(nèi)部不能使用未靜態(tài)的或者未實(shí)例的類對(duì)象,因此也可以這樣實(shí)現(xiàn)
public?class?Singleton?{
??????private?volatile?static?Singleton?instance;
??????private?Singleton()?{
??????}
??????public?static?Singleton?getInstance()?{
??????????if?(instance?==?null)?{
??????????????synchronized?(Singleton.class)?{
?????????????????if?(instance?==?null)?{
?????????????????????instance?=?new?Singleton();
?????????????????}
?????????????}
?????????}
?????????return?instance;
?????}
?}就算在單例類被實(shí)例化時(shí)有多個(gè)線程,同時(shí)執(zhí)行了if (instance == null)的判斷,但同一時(shí)間點(diǎn)只有一個(gè)線程可以獲得鎖后進(jìn)入臨界區(qū)。通過if (instance == null)判斷的每個(gè)線程會(huì)依次獲得鎖進(jìn)入臨界區(qū),所以進(jìn)入臨界區(qū)后還要再判斷一次單例類是否已被其它線程實(shí)例化,以避免多次實(shí)例化。由于雙重加鎖實(shí)現(xiàn)僅在實(shí)例化單例類時(shí)需要加鎖,所以相較于懶漢式第2種實(shí)現(xiàn)方式會(huì)帶來性能上的提升。另外需要注意的是雙重加鎖要對(duì) instance 域加上 volatile關(guān)鍵字修飾符。由于 synchronized 并不是對(duì) instance 實(shí)例進(jìn)行加鎖(因?yàn)楝F(xiàn)在還并沒有實(shí)例),所以線程在執(zhí)行完?instance = new Singleton();修改 instance 的值后,應(yīng)該將修改后的 instance 立即寫入主存(main memory),而不是暫時(shí)存在寄存器或者高速緩沖區(qū)(caches)中,以保證新的值對(duì)其它線程可見。

掃碼二維碼
獲取更多精彩
Java樂園

