(其實(shí)設(shè)計(jì)模式應(yīng)該從屬于java伶跷,但是會(huì)專門針對(duì)android做相應(yīng)的解釋,所以就取名為android設(shè)計(jì)模式~)
一.單例模式的介紹
單例模式是應(yīng)用最廣的模式之一,在應(yīng)用這個(gè)模式的時(shí)候盾沫,單例對(duì)象的類必須保證只有一個(gè)實(shí)例存在。在android中的應(yīng)用場(chǎng)景例如整個(gè)app只有一個(gè)application對(duì)象殿漠,只有一個(gè)ImageLoader對(duì)象等赴精。
二.單例模式下的各種實(shí)現(xiàn)方式
1.餓漢模式
public class Singleton {
private Singleton() {} //在該類初始化的時(shí)候就會(huì)自行實(shí)例化
private static final Singleton single = new Singleton();
public static Singleton getInstance() {
return single;
}
}
2.懶漢模式
public class Singleton {
private Singleton(){}
private final static Singleton mInstance; //沒有care線程安全的問題
public static Singleton getInstance() {
if(mInstance == null){
mInstance = new Singleton();
}
return mInstance;
}
}
Tips:不論是餓漢模式還是懶漢模式,可能在你的app中都占有一席之地绞幌。那么我們先來對(duì)比下兩者的區(qū)別蕾哟,首先餓漢模式會(huì)在該類初始化的時(shí)候就自動(dòng)實(shí)例化,而懶漢模式則會(huì)在對(duì)應(yīng)調(diào)用getInstance方法時(shí)才會(huì)對(duì)應(yīng)的實(shí)例化莲蜘,實(shí)現(xiàn)了實(shí)例的延時(shí)加載谭确。設(shè)想如果該實(shí)例在app中不一定被使用到,那么使用懶漢模式就可以節(jié)省內(nèi)存票渠。但是懶漢模式會(huì)在第一次獲取實(shí)例時(shí)較為耗時(shí)逐哈,餓漢模式由于在初始化類時(shí)就進(jìn)行了實(shí)例化,第一次獲取實(shí)例就不會(huì)耗時(shí)问顷。
以上是針對(duì)餓漢模式和懶漢模式之間的區(qū)別做的分析昂秃,接下來我們來關(guān)注之前代碼中對(duì)于懶漢模式線程不安全的問題薯鼠。分別提供以下幾種解決方案來進(jìn)行對(duì)比:
2.1.在getInstance方法上加同步鎖
public class Singleton {
private Singleton(){}
private final static Singleton mInstance; //加上同步鎖
public static synchronized Singleton getInstance() {
if(mInstance == null){
mInstance = new Singleton();
}
return mInstance;
}
}
這種方法雖然解決了線程安全的問題,但是單例模式一般都是應(yīng)用在一些會(huì)被頻繁調(diào)用的場(chǎng)景上的械蹋,如果在每次獲取實(shí)例的時(shí)候都需要去進(jìn)行線程同步出皇,那會(huì)增加不小的開銷,會(huì)使單例的獲取變的緩慢哗戈,這樣就得不償失了郊艘。那么我們繼續(xù)改進(jìn),看下面的方法:
2.2.Double Check Lock(DCL)實(shí)現(xiàn)單例
public class Singleton {
private Singleton(){}
private final static Singleton mInstance;
/*雙重鎖定:只在第一次初始化的時(shí)候加上同步鎖*/
public static Singleton getInstance() {
if(mInstance == null){
synchronized(Singleton.class){
if(mInstance == null){
mInstance = new Singleton();
}
}
}
return mInstance;
}
}```
這種雙重鎖定的方式唯咬,避免了每次獲取實(shí)例時(shí)不必要的同步操作纱注,只在第一次獲取實(shí)例的時(shí)候才進(jìn)行同步,將開銷減到了最小胆胰,并且保證了線程安全狞贱。但是,真的是線程安全了么蜀涨?問題其實(shí)出在mInstance = new Singleton();這句代碼瞎嬉,雖然它只是一句代碼,但是實(shí)際上它不是一個(gè)原子操作厚柳,這句代碼最終會(huì)被編譯成多條匯編指令氧枣,它大致做了3件事情:
(1)給Singleton的實(shí)例分配內(nèi)存;
(2)調(diào)用Singleton()的構(gòu)造函數(shù),初始化成員字段;
(3)將mInstance對(duì)象指向分配的內(nèi)存空間(此時(shí)mInstance就不是null了)别垮。
由于Java編譯器允許處理器亂序執(zhí)行便监,以及JDK1.5之前JMM(Java Memory Model,即Java內(nèi)存模型)中Cache碳想、寄存器到內(nèi)存回寫順序的規(guī)定烧董,上面的第二和第三的順序是無法保證的。也就是說胧奔,執(zhí)行順序可能是1-2-3也可能是1-3-2逊移。如果是后者,并且在3執(zhí)行完畢葡盗、2未執(zhí)行之前螟左,被切換到另一個(gè)線程上啡浊,就會(huì)出問題觅够。但是在你的app沒有太多的高并發(fā)存在時(shí),這種模式已經(jīng)可以完全滿足大多數(shù)開發(fā)者的需求巷嚣。那么一定還有更好的:
#### 2.3.靜態(tài)內(nèi)部類單例模式
```java
public class Singleton {
private Singleton(){}
private final static Singleton mInstance;
public static Singleton getInstance() {
return SingletonHolder.mInstance;
}
private static class SingletonHolder {
private final static Singleton mInstance = new Singleton();
}
}
當(dāng)?shù)谝淮渭虞dSingleton類的時(shí)候并不會(huì)初始化mInstance喘先,只有在第一次調(diào)用getInstance方法時(shí)才會(huì)導(dǎo)致mInstance被初始化。因此廷粒,第一次調(diào)用getInstance方法會(huì)導(dǎo)致虛擬機(jī)加載SingletonHolder類窘拯,這種方式不僅能夠確保線程安全红且,也能夠保證單例對(duì)象的唯一性,同時(shí)也延遲了單例的實(shí)例化涤姊,所以這是推薦使用的單例模式實(shí)現(xiàn)方式暇番。Tip:java中的枚舉其實(shí)也是單例的一種實(shí)現(xiàn)方式
三.結(jié)論
之前在對(duì)單例的了解并沒有特別的系統(tǒng),這次梳理了下思喊,發(fā)現(xiàn)其實(shí)自己的工程中還是有很多不考慮線程安全的單例實(shí)現(xiàn)的壁酬,雖然在沒有并發(fā)的情況下可能沒有太大的影響,但是程序是需要有超前意識(shí)的恨课,推薦大家也使用2.3的單例實(shí)現(xiàn)方式舆乔。