關(guān)于java的單例設(shè)計(jì)模式,是項(xiàng)目當(dāng)中很常用的設(shè)計(jì)模式。當(dāng)某個(gè)資源,或者某個(gè)實(shí)例 ,整個(gè)項(xiàng)目只使用一份的情況下,我們就需要用這個(gè)去創(chuàng)建一個(gè)單例,例如工具類等,都是要用到這種模式的。項(xiàng)目中對(duì)于這些工具類,往往只需要維持一個(gè)對(duì)象,然后一直用這個(gè)對(duì)象就可以了,以此減少內(nèi)存的開支。
class SingleTonClass {
//餓漢式單例模式 提前創(chuàng)建對(duì)象
private static SingleTonClass singleTonClass = new SingleTonClass();
private SingleTonClass() {
}
public static SingleTonClass getInstance() {
return singleTonClass;
}
}
class SimpleClass {
//懶漢式單例模式 等到需求時(shí)再去判斷是否需要?jiǎng)?chuàng)建對(duì)象
private static SimpleClass simpleClass;
private SimpleClass() {
}
public static SimpleClass getInstance() {
if (simpleClass == null) {//懶漢式
simpleClass = new SimpleClass();
}
return simpleClass;
}
}
上述寫法,如果在線程并發(fā)執(zhí)行的狀態(tài)下,很可能會(huì)出現(xiàn)多個(gè)實(shí)例被創(chuàng)建。
所以我們可以改造getInstance()方法,去適應(yīng)多線程情況。
public static SimpleClass getInstance() {
if (simpleClass == null) {
synchronized (SimpleClass.class) {//減小同步區(qū)域 更好的優(yōu)化性能
if (simpleClass == null) {
simpleClass = new SimpleClass();
}
}
}
return simpleClass;
}
雙重鎖定使得這個(gè)函數(shù)更加的安全,但是依然有問題
因?yàn)閟impleClass = new SimpleClass();這段代碼 ,并不是原子性操作,他包含好幾個(gè)步驟:
- 為對(duì)象分配內(nèi)存
- 初始化實(shí)例對(duì)象
- 把引用instance指向分配的內(nèi)存空間
這個(gè)三個(gè)步驟并不能保證按序執(zhí)行,處理器會(huì)進(jìn)行指令重排序優(yōu)化,存在這樣的情況:
優(yōu)化重排后執(zhí)行順序?yàn)椋?,3,2, 這樣在線程1執(zhí)行到3時(shí),instance已經(jīng)不為null了,線程2此時(shí)判斷instance!=null,則直接返回instance引用,但現(xiàn)在實(shí)例對(duì)象還沒有初始化完畢,此時(shí)線程2使用instance可能會(huì)造成程序崩潰。
現(xiàn)在要解決的問題就是怎樣限制處理器進(jìn)行指令優(yōu)化重排。
答案就是用volatile關(guān)鍵字 ,他提供了線程之間修改可見性。
class SimpleClass {
private static volatile SimpleClass simpleClass;
private SimpleClass() {
}
public static SimpleClass getInstance() {
if (simpleClass == null) {
synchronized (SimpleClass.class) {
if (simpleClass == null) {
simpleClass = new SimpleClass();
}
}
}
return simpleClass;
}
}
解釋一下volatile關(guān)鍵字:
1.保證可見性
可以保證在多線程環(huán)境下,變量的修改可見性。每個(gè)線程都會(huì)在工作內(nèi)存(類似于寄存器和高速緩存),實(shí)例對(duì)象都存放在主內(nèi)存中,在每個(gè)線程要使用的時(shí)候把主內(nèi)存中的內(nèi)容拷貝到線程的工作內(nèi)存中。使用volatile關(guān)鍵字修飾后的變量,保證每次修改了變量需要立即寫回主內(nèi)存中,同時(shí)通知所有的該對(duì)變量的緩存失效,保證緩存一致性,其他線程需要使用該共享變量時(shí)就要重新從住內(nèi)存中獲取最新的內(nèi)容拷貝到工作內(nèi)存中供處理器使用。這樣就可以保證變量修改的可見性了。但volatile不能保證原子性,比如++操作。
2.提供內(nèi)存屏障
volatile關(guān)鍵字能夠通過提供內(nèi)存屏障,來保證某些指令順序處理器不能夠優(yōu)化重排,編譯器在生成字節(jié)碼時(shí),會(huì)在指令序列中插入內(nèi)存屏障來禁止特定類型的處理器重排序。
下面是保守策略插入內(nèi)存屏障:
在每個(gè)volatile寫操作的前面插入一個(gè)StoreStore屏障。
在每個(gè)volatile寫操作的后面插入一個(gè)StoreLoad屏障。
在每個(gè)volatile讀操作的前面插入一個(gè)LoadLoad屏障。
在每個(gè)volatile讀操作的后面插入一個(gè)LoadLoad屏障。
這樣可以保證在volatile關(guān)鍵字修飾的變量的賦值和讀取操作前后兩邊的大的順序不會(huì)改變,
在內(nèi)存屏障前面的順序可以交換,屏障后面的也可以換序,但是不能跨越內(nèi)存屏障重排執(zhí)行順序。
好了,現(xiàn)在來看上面的單例模式,這樣就可以保證3步驟(instance賦值操作)是保持最后一步完成,
這樣就不會(huì)出現(xiàn)instance在對(duì)象沒有初始化時(shí)就不為null的情況了。這樣也就實(shí)現(xiàn)了正確的單例模式了。
還有一種枚舉,也是單例模式的實(shí)現(xiàn):
public enum SingleEnum{
INSTANCE; //不建議用
}
關(guān)于單例模式在android方面的應(yīng)用:
單例模式一般運(yùn)用在一些工具類上面。還有,界面之間傳輸數(shù)據(jù),有可能會(huì)因?yàn)閿?shù)據(jù)量太大,造成TransactionTooLargeException異常的,所以這個(gè)時(shí)候,就需要通過一個(gè)幫助類去存儲(chǔ)這段數(shù)據(jù),為了保證數(shù)據(jù)的正確性,這個(gè)時(shí)候必定就是要用到單例模式的。