
1 單例模式初識(shí):
1.1 單例模式:
單例模式是指確保某一個(gè)類(lèi)只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例;那為什么要有單例模式呢?比如我們?cè)诖a中有一個(gè)網(wǎng)絡(luò)請(qǐng)求的實(shí)現(xiàn)類(lèi),那在業(yè)務(wù)需求的場(chǎng)景中,會(huì)有頻繁的網(wǎng)絡(luò)請(qǐng)求,那此時(shí)如果有一個(gè)網(wǎng)絡(luò)請(qǐng)求,就去new一個(gè)網(wǎng)絡(luò)請(qǐng)求的實(shí)現(xiàn)類(lèi),這樣可能會(huì)造成資源的浪費(fèi),所以引入了單例模式;
1.2 單例模式的特征或者說(shuō)創(chuàng)建一個(gè)單例模式類(lèi)的要求:
- 構(gòu)造方法不對(duì)外開(kāi)放的(private修飾);
- 建立一個(gè)類(lèi)靜態(tài)變量,持有一個(gè)自己的實(shí)例;
- 通過(guò)一個(gè)靜態(tài)方法或者枚舉返回單列類(lèi)的對(duì)象;
- 注意多線(xiàn)程的場(chǎng)景也要保證單例;
- 如果單例可以序列化,那要注意單例對(duì)象在反序列化時(shí)不會(huì)重新創(chuàng)建對(duì)象;
1.3 單例模式的兩種類(lèi)型:
1.3.1 餓漢式單例模式
public class singletonMode {
private singletonMode (){} //構(gòu)造方法private修飾,不對(duì)外開(kāi)放
private static mSingleton = new singletonMode();//靜態(tài)變量實(shí)例化
public static singletonMode getInstance(){//通過(guò)一個(gè)靜態(tài)方法返回單列類(lèi)的對(duì)象
retrun mSingleton;
}
上面為餓漢式單例模式的實(shí)現(xiàn)方式,從代碼中可以看出,在類(lèi)加載的時(shí)候mSingleton就已經(jīng)被實(shí)例化了,無(wú)論用不用都會(huì)被加載,所以可能會(huì)造成資源浪費(fèi)或者加載緩慢(比如說(shuō)構(gòu)造方法中做的操作比較多);但是它是線(xiàn)程安全的;
1.3.2 懶漢式單例模式
針對(duì)餓漢式單例模式的缺點(diǎn),則引出了如下的懶漢式單例模式:
public class singletonMode2 {
private singletonMode2 (){} //構(gòu)造方法private修飾,不對(duì)外開(kāi)放
private static mSingleton2 = null();//靜態(tài)變量
public static singletonMode2 getInstance(){//通過(guò)一個(gè)靜態(tài)方法返回單列類(lèi)的對(duì)象
if(mSingleton2 == null)//當(dāng)實(shí)例對(duì)象為null的時(shí)候才去new
mSingleton2 = new singletonMode2();
retrun mSingleton2;
}
以上可以解決餓漢式單例模式在類(lèi)加載的時(shí)候就實(shí)例化靜態(tài)變量,引起資源浪費(fèi)的問(wèn)題,此處是在用的時(shí)候通過(guò)getInstance去拿時(shí),才去new;但是通過(guò)仔細(xì)觀(guān)察,懶漢式單例模式存在線(xiàn)程不安全問(wèn)題,所以需要進(jìn)一步優(yōu)化;
優(yōu)化方式一:對(duì)getInstance靜態(tài)方法添加synchronized關(guān)鍵字
public class singletonMode3 {
private singletonMode3 (){} //構(gòu)造方法private修飾,不對(duì)外開(kāi)放
private static mSingleton3 = null();//靜態(tài)變量
public static synchronized singletonMode3 getInstance(){//通過(guò)一個(gè)靜態(tài)方法返回單列類(lèi)的對(duì)象
if(mSingleton3 == null)//當(dāng)實(shí)例對(duì)象為null的時(shí)候才去new
mSingleton3 = new singletonMode3();
retrun mSingleton3;
}
相比之下,線(xiàn)程安全提高,但是synchronized是加在靜態(tài)方法上的,同步鎖的顆粒度有點(diǎn)大,所以進(jìn)一步優(yōu)化;
優(yōu)化方式二:這就引出了 雙重校驗(yàn)DCL
public class singletonMode4 {
private singletonMode4 (){} //構(gòu)造方法private修飾,不對(duì)外開(kāi)放
private static mSingleton4 = null();//靜態(tài)變量
public static singletonMode4 getInstance(){//通過(guò)一個(gè)靜態(tài)方法返回單列類(lèi)的對(duì)象
if(mSingleton4 == null){//第一層校驗(yàn)
synchronized(singletonMode4 .class){
if(mSingleton4 == null){//第二層校驗(yàn)
mSingleton4 = new singletonMode4();
}
}
}
retrun mSingleton4;
}
采用方式二則降低了同步鎖的顆粒度,同時(shí)也考慮了線(xiàn)程安全問(wèn)題;
注意:但是在JAVA虛擬機(jī)中,大家都知道m(xù)Singleton4 = new singletonMode4()一句代碼它主要包括三個(gè)內(nèi)容:
- mSingleton4 實(shí)例分配對(duì)象
- 調(diào)用singletonMode4的構(gòu)造方法,初始化成員字段
- 將singletonMode4對(duì)象賦值給mSingleton4
由于在JDK中會(huì)進(jìn)行指令重排,所以有可能會(huì)導(dǎo)致DCL失效問(wèn)題,所以在JDK1.5后引入了volatile禁止指令重排,進(jìn)而保證了DCL雙重檢測(cè)的有效性;
2 餓漢式與懶漢式單例模式的應(yīng)用:
餓漢式
在類(lèi)加載的時(shí)候就會(huì)實(shí)例化對(duì)象,無(wú)論是否會(huì)用到這個(gè)對(duì)象,都會(huì)加載。如果在構(gòu)造方法里寫(xiě)了性能消耗較大,占時(shí)較久的代碼,那么就會(huì)在啟動(dòng)的時(shí)候感覺(jué)稍微有些卡頓。
懶漢式
是延遲加載的方式,只有使用的時(shí)候才會(huì)加載。 并且有線(xiàn)程安全的考量。使用懶漢式,在啟動(dòng)的時(shí)候,會(huì)感覺(jué)到比餓漢式略快,因?yàn)椴](méi)有做對(duì)象的實(shí)例化。 但是在第一次調(diào)用的時(shí)候,會(huì)進(jìn)行實(shí)例化操作,感覺(jué)上就略慢。
在實(shí)際應(yīng)用中看業(yè)務(wù)需求,如果業(yè)務(wù)上允許有比較充分的啟動(dòng)和初始化時(shí)間,就使用餓漢式,否則就使用懶漢式
3 單例模式擴(kuò)展
3.1 單例模式的其他實(shí)現(xiàn)方式:
方式一:靜態(tài)內(nèi)部類(lèi)單例模式:
public class singletonMode5 {
private singletonMode5 (){} //構(gòu)造方法private修飾,不對(duì)外開(kāi)放
private static class singletonModeInner {
private static mSingleton5 = new singletonMode5();
}
public static singletonMode5 getInstance(){//通過(guò)一個(gè)靜態(tài)方法返回單列類(lèi)的對(duì)象
retrun singletonMode5.singletonModeInner.mSingleton5 ;
}
}
方式二:使用枚舉:
public class Single {
private Single(){}
public enum SingleEnum {
singleHandler;
private Single single;
private SingleEnum () {
single = new Single();
}
public Single getSingle() {
return single;
}
}
public static Single getInstacne() {
return SingleEnum.singleHandler.getSingle();
}
}
對(duì)于以上兩種實(shí)現(xiàn)方式,枚舉是線(xiàn)程安全的。另外還有一些比如說(shuō)通過(guò)容器的方式來(lái)實(shí)現(xiàn)單例,有興趣的可以進(jìn)一步了解一下;
3.2 可序列化的單例類(lèi)
如果我們的類(lèi)是可序列化的,那么在反序列化時(shí)會(huì)破壞單例,生成新的單列類(lèi);
private Object readResolve(){
System.out.println("read resolve");
return instance;//返回之前定義的單例類(lèi)對(duì)象
}
這中情況,可以通過(guò)重寫(xiě)readResolve()方法,此方法中返回了單例類(lèi)的對(duì)象。具體readResolve解析可參考這篇文章:https://blog.csdn.net/weixin_45433031/article/details/115364766?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-1&spm=1001.2101.3001.4242
小結(jié):本章節(jié)了解了設(shè)計(jì)模式中的單例模式,單例模式在我們的實(shí)際開(kāi)發(fā)中是很常見(jiàn)的,大家可以根據(jù)自己業(yè)務(wù)需求,選擇不同的實(shí)現(xiàn)方式,那重點(diǎn)關(guān)注一下DCL雙重檢測(cè);