對于一個(gè)軟件系統(tǒng)的某些類而言,我們無須創(chuàng)建多個(gè)實(shí)例。舉個(gè)大家都熟知的例子——Windows任務(wù)管理器,我們可以做一個(gè)這樣的嘗試,在Windows的“任務(wù)欄”的右鍵彈出菜單上多次點(diǎn)擊“啟動任務(wù)管理器”,看能否打開多個(gè)任務(wù)管理器窗口?通常情況下,無論我們啟動任務(wù)管理多少次,Windows系統(tǒng)始終只能彈出一個(gè)任務(wù)管理器窗口,也就是說在一個(gè)Windows系統(tǒng)中,任務(wù)管理器存在唯一性。
實(shí)際開發(fā)中,我們也經(jīng)常遇到類似的情況,為了節(jié)約系統(tǒng)資源,有時(shí)需要確保系統(tǒng)中某個(gè)類只有唯一一個(gè)實(shí)例,當(dāng)這個(gè)唯一實(shí)例創(chuàng)建成功之后,我們無法再創(chuàng)建一個(gè)同類型的其他對象,所有的操作都只能基于這個(gè)唯一實(shí)例。為了確保對象的唯一性,我們可以通過單例模式來實(shí)現(xiàn),這就是單例模式的動機(jī)所在。

單例模式定義如下: 單例模式(Singleton Pattern):確保某一個(gè)類只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例,這個(gè)類稱為單例類,它提供全局訪問的方法。單例模式是一種對象創(chuàng)建型模式。
單例模式有三個(gè)要點(diǎn):

在單例類的內(nèi)部實(shí)現(xiàn)只生成一個(gè)實(shí)例,同時(shí)它提供一個(gè)靜態(tài)的getInstance()工廠方法,讓客戶可以訪問它的唯一實(shí)例;為了防止在外部對其實(shí)例化,將其構(gòu)造函數(shù)設(shè)計(jì)為私有;在單例類內(nèi)部定義了一個(gè)Singleton類型的靜態(tài)對象,作為外部共享的唯一實(shí)例。
單例模式一般分為兩種,分別是餓漢式與懶漢式,下面就分別講解這兩種模式,并且詳解餓漢式的雙重校驗(yàn)鎖
餓漢式對于餓漢式來說,它在定義靜態(tài)變量的時(shí)候?qū)嵗瘑卫悾虼嗽陬惣虞d的時(shí)候就已經(jīng)創(chuàng)建了單例對象。而且該單例對象用final修飾,之后每次獲取直接返回即可。
public class HungrySingleton {private static final HungrySingleton singleton=new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){return singleton;
}
}懶漢式(雙重校驗(yàn)鎖)懶漢式單例在第一次調(diào)用getInstance()方法時(shí)實(shí)例化,在類加載時(shí)并不自行實(shí)例化,這種技術(shù)又稱為延遲加載(Lazy Load)技術(shù),即需要的時(shí)候再加載實(shí)例。
先看一下在單線程情況下的懶漢式單例:
public class LazySingleton {private static LazySingleton singleton=null;
private LazySingleton(){}
public static LazySingleton getInstance(){if(singleton==null){singleton=new LazySingleton();
}
return singleton;
}
}可以看到,在獲取實(shí)例的時(shí)候,會先判斷一下當(dāng)前的靜態(tài)變量singleton是否為null,如果為null,則為其初始化。如果不為null,則直接返回。
但是這樣的程序在多線程環(huán)境下會出現(xiàn)問題!!!
在多線程環(huán)境下這樣的代碼會出現(xiàn)問題,因此我們考慮給getInstance()方法加上同步鎖,防止多個(gè)線程同時(shí)訪問getInstance()方法,如下。
public class LazySingleton {private static LazySingleton singleton=null;
private LazySingleton(){}
public static synchronized LazySingleton getInstance(){if(singleton==null){singleton=new LazySingleton();
}
return singleton;
}
}但是這樣一來,每次調(diào)用getInstance()時(shí)都需要進(jìn)行線程鎖定判斷,在多線程高并發(fā)訪問環(huán)境中,將會導(dǎo)致系統(tǒng)性能大大降低。那么如何解決該問題呢?有人提出了雙重校驗(yàn)鎖:
在加鎖之前先判斷一下該靜態(tài)變量是否為null。如果不為null就不需要再加鎖進(jìn)行初始化了。這樣在高并發(fā)的環(huán)境下就不會出現(xiàn)頻繁獲取鎖的情況了。
public class LazySingletonSyn {private static LazySingletonSyn singleton=null;
private LazySingletonSyn(){}
public static LazySingletonSyn getInstance(){if(singleton==null){synchronized (LazySingletonSyn.class){if(singleton==null){ // 標(biāo)記點(diǎn) 1
singleton=new LazySingletonSyn();
}
}
}
return singleton;
}
}現(xiàn)在程序是完美的了嗎?
依然不是!問題出現(xiàn)在singleton=new LazySingletonSyn();語句
由于現(xiàn)代的處理器大多采用指令級并行技術(shù)。為了提高指令的執(zhí)行效率,在指令執(zhí)行的階段可能會出現(xiàn)重排序的現(xiàn)象。
我們看上面代碼的標(biāo)記點(diǎn)1singleton=new LazySingletonSyn();
該語句在執(zhí)行的時(shí)候會被分解為三條(偽)指令:
memory=allocate(); //1.分配對象的內(nèi)存空間
ctorInstance(memory); //2. 初始化對象
instance=memory; //3. 設(shè)置instance指向剛分配的內(nèi)存地址在上面的偽代碼中,其中2與3可能會被重排序。
這里涉及到了as-if-serial概念。as-if-serial概念是指不管怎么重排序,單線程程序的執(zhí)行結(jié)果不能被改變。而在該語句中,將2,3的執(zhí)行順序改變之后,在單線程的情況下該程序的運(yùn)行結(jié)果不會被改變。因此2,3可能重排序
一旦被重排序,就可能出現(xiàn)以下的結(jié)果:
如果線程A,B按照下圖的時(shí)間執(zhí)行,那么B線程將會得到一個(gè)還沒有被初始化的對象!!
問題就出現(xiàn)在2,3的重排序!那么我們只需要禁止2,3重排序即可。
雙重校驗(yàn)鎖實(shí)現(xiàn)懶漢式
我們只需要對上面的代碼進(jìn)行很小的改動(將singleton聲明為volatile),就可以實(shí)現(xiàn)線程安全的懶漢式單例。
public class LazySingletonSyn {private volatile static LazySingletonSyn singleton=null;
private LazySingletonSyn(){}
public static LazySingletonSyn getInstance(){if(singleton==null){synchronized (LazySingletonSyn.class){if(singleton==null){singleton=new LazySingletonSyn();
}
}
}
return singleton;
}
}解釋如下:
由于singleton被volatile修飾,那么為了實(shí)現(xiàn)volatile的內(nèi)存語義(保證singleton在多線程環(huán)境下對共享內(nèi)存的可見性),編譯器在生成字節(jié)碼的時(shí)候,會在指令序列中插入內(nèi)存屏障來禁止特定的指令重排序。
由于singleton=new LazySingletonSyn();是一個(gè)寫操作,在該操作的指令之后,JMM(Java內(nèi)存模型)會插入一個(gè)storeload內(nèi)存屏障。
此時(shí)的內(nèi)存指令變成:
memory=allocate(); //1.分配對象的內(nèi)存空間
ctorInstance(memory); //2. 初始化對象
instance=memory; //3. 設(shè)置instance指向剛分配的內(nèi)存地址
storeload; //4. storeload內(nèi)存屏障該指令(storeload)會保證在屏障之前的所有內(nèi)存訪問指令全部完成之后,再執(zhí)行該屏障的之后的語句。因此當(dāng)線程B再嘗試singleton==null的時(shí)候,線程A的singleton=new LazySingletonSyn();以及全部執(zhí)行完成了。因此就不會再出現(xiàn)上面的由于指令2,3重排序?qū)е碌膯栴}了。
其實(shí)就是一句話:
JMM會禁止volatile寫與其之后可能存在的volatile讀/寫重排序。因此不會存在上面的圖出現(xiàn)的情況!
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧
當(dāng)前題目:兩種單例模式詳解(內(nèi)含懶漢式的雙重校驗(yàn)鎖詳解)-創(chuàng)新互聯(lián)
URL標(biāo)題:http://www.chinadenli.net/article24/docgce.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供用戶體驗(yàn)、商城網(wǎng)站、關(guān)鍵詞優(yōu)化、域名注冊、云服務(wù)器、軟件開發(fā)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容