對(duì)于一個(gè)軟件系統(tǒng)的某些類而言,我們無須創(chuàng)建多個(gè)實(shí)例。舉個(gè)大家都熟知的例子——Windows任務(wù)管理器,我們可以做一個(gè)這樣的嘗試,在Windows的“任務(wù)欄”的右鍵彈出菜單上多次點(diǎn)擊“啟動(dòng)任務(wù)管理器”,看能否打開多個(gè)任務(wù)管理器窗口?通常情況下,無論我們啟動(dòng)任務(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è)同類型的其他對(duì)象,所有的操作都只能基于這個(gè)唯一實(shí)例。為了確保對(duì)象的唯一性,我們可以通過單例模式來實(shí)現(xiàn),這就是單例模式的動(dòng)機(jī)所在。
單例模式定義如下: 單例模式(Singleton Pattern):確保某一個(gè)類只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例,這個(gè)類稱為單例類,它提供全局訪問的方法。單例模式是一種對(duì)象創(chuàng)建型模式。
單例模式有三個(gè)要點(diǎn):
在單例類的內(nèi)部實(shí)現(xiàn)只生成一個(gè)實(shí)例,同時(shí)它提供一個(gè)靜態(tài)的getInstance()工廠方法,讓客戶可以訪問它的唯一實(shí)例;為了防止在外部對(duì)其實(shí)例化,將其構(gòu)造函數(shù)設(shè)計(jì)為私有;在單例類內(nèi)部定義了一個(gè)Singleton類型的靜態(tài)對(duì)象,作為外部共享的唯一實(shí)例。
單例模式一般分為兩種,分別是餓漢式與懶漢式,下面就分別講解這兩種模式,并且詳解餓漢式的雙重校驗(yàn)鎖
餓漢式對(duì)于餓漢式來說,它在定義靜態(tài)變量的時(shí)候?qū)嵗瘑卫悾虼嗽陬惣虞d的時(shí)候就已經(jīng)創(chuàng)建了單例對(duì)象。而且該單例對(duì)象用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í)候,會(huì)先判斷一下當(dāng)前的靜態(tài)變量singleton是否為null,如果為null,則為其初始化。如果不為null,則直接返回。
但是這樣的程序在多線程環(huán)境下會(huì)出現(xiàn)問題?。?!
在多線程環(huán)境下這樣的代碼會(huì)出現(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)境中,將會(huì)導(dǎo)致系統(tǒng)性能大大降低。那么如何解決該問題呢?有人提出了雙重校驗(yàn)鎖:
在加鎖之前先判斷一下該靜態(tài)變量是否為null。如果不為null就不需要再加鎖進(jìn)行初始化了。這樣在高并發(fā)的環(huán)境下就不會(huì)出現(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)代的處理器大多采用指令級(jí)并行技術(shù)。為了提高指令的執(zhí)行效率,在指令執(zhí)行的階段可能會(huì)出現(xiàn)重排序的現(xiàn)象。
我們看上面代碼的標(biāo)記點(diǎn)1singleton=new LazySingletonSyn();
該語句在執(zhí)行的時(shí)候會(huì)被分解為三條(偽)指令:
memory=allocate(); //1.分配對(duì)象的內(nèi)存空間
ctorInstance(memory); //2. 初始化對(duì)象
instance=memory; //3. 設(shè)置instance指向剛分配的內(nèi)存地址
在上面的偽代碼中,其中2與3可能會(huì)被重排序。
這里涉及到了as-if-serial概念。as-if-serial概念是指不管怎么重排序,單線程程序的執(zhí)行結(jié)果不能被改變。而在該語句中,將2,3的執(zhí)行順序改變之后,在單線程的情況下該程序的運(yùn)行結(jié)果不會(huì)被改變。因此2,3可能重排序
一旦被重排序,就可能出現(xiàn)以下的結(jié)果:
如果線程A,B按照下圖的時(shí)間執(zhí)行,那么B線程將會(huì)得到一個(gè)還沒有被初始化的對(duì)象??!
問題就出現(xiàn)在2,3的重排序!那么我們只需要禁止2,3重排序即可。
雙重校驗(yàn)鎖實(shí)現(xiàn)懶漢式
我們只需要對(duì)上面的代碼進(jìn)行很小的改動(dòng)(將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)境下對(duì)共享內(nèi)存的可見性),編譯器在生成字節(jié)碼的時(shí)候,會(huì)在指令序列中插入內(nèi)存屏障來禁止特定的指令重排序。
由于singleton=new LazySingletonSyn();
是一個(gè)寫操作,在該操作的指令之后,JMM(Java內(nèi)存模型)會(huì)插入一個(gè)storeload內(nèi)存屏障。
此時(shí)的內(nèi)存指令變成:
memory=allocate(); //1.分配對(duì)象的內(nèi)存空間
ctorInstance(memory); //2. 初始化對(duì)象
instance=memory; //3. 設(shè)置instance指向剛分配的內(nèi)存地址
storeload; //4. storeload內(nèi)存屏障
該指令(storeload)會(huì)保證在屏障之前的所有內(nèi)存訪問指令全部完成之后,再執(zhí)行該屏障的之后的語句。因此當(dāng)線程B再嘗試singleton==null
的時(shí)候,線程A的singleton=new LazySingletonSyn();
以及全部執(zhí)行完成了。因此就不會(huì)再出現(xiàn)上面的由于指令2,3重排序?qū)е碌膯栴}了。
其實(shí)就是一句話:
JMM會(huì)禁止volatile寫與其之后可能存在的volatile讀/寫重排序。因此不會(huì)存在上面的圖出現(xiàn)的情況!
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級(jí)服務(wù)器適合批量采購,新人活動(dòng)首月15元起,快前往官網(wǎng)查看詳情吧