作者 : 夏至 歡迎轉(zhuǎn)載,也請保留這段申明
http://blog.csdn.net/u011418943/article/details/60139644
上一篇講到策略模式,變動的代碼需要用到策略模式,感興趣的小伙伴可以看看.
傳送門:Android 常用設(shè)計模式之 -- 策略模式
單例模式的定義就不解釋過多了,相信很多小伙伴在設(shè)計的時候,都用到這個模式;常用的場景為 數(shù)據(jù)庫的訪問,文件流的訪問以及網(wǎng)絡(luò)連接池的訪問等等,在這些場景中,我們都希望實例只有一個,除了減少內(nèi)存開銷之外,也防止防止多進程修改文件錯亂和數(shù)據(jù)庫鎖住的問題。
在這一篇文章中,我將帶你分析 android 常見的集中單例模式,并詳細(xì)分析他們的優(yōu)缺點。讓大家在以后的選擇單例中,可以根據(jù)實際情況選擇。
當(dāng)然,如有錯誤,也歡迎指正。
下面是介紹:
1、餓漢式
就是初始化的時候,直接初始化,典型的以時間換空間的做法。
public class SingleMode{
//構(gòu)造方法私有化,這樣外界就不能訪問了
private SingleMode(){
};
//當(dāng)類被初始化的時候,就直接new出來
private static SingleMode instance = new SingleMode();
//提供一個方法,給他人調(diào)用
public static SingleMode getInstance(){
return instance;
}
}
餓漢式的好處是線程安全,因為虛擬機保證只會裝載一次,再裝載類的時候,是不會并發(fā)的,這樣就保證了線程安全的問題。
但缺點也很明顯,一初始化就實例占內(nèi)存了,但我褲子還沒脫,不想用呢。
2、懶漢式
為了解決上面的問題,開了懶漢式,就是需要使用的時候,才去加載;
public class SingleMode{
//構(gòu)造方法私有化,這樣外界就不能訪問了
private SingleMode(){
};
private static SingleMode mSingleMode;
public static SingleModegetInstance(){ //這里就是延時加載的意思
if (mSingleMode == null){
mSingleMode = new SingleMode();
}
return mSingleMode;
}
}
懶漢式如上所示,
優(yōu)點:
我們只需要在用到的時候,才申請內(nèi)存,且可以從外部獲取參數(shù)再實例化,這點是懶漢式的最大優(yōu)點了
缺點:
單線程只實例了一次,如果是多線程了,那么它會被多次實例
至于問什么說它是線程不安全的呢?先下面這張圖:
我們假設(shè)一下,有兩個線程,A和B都要初始化這個實例;此時 A 比較快,已經(jīng)判斷 mSingleMode 為null,正在創(chuàng)建實例,而 B 這時候也再判斷,但此時 A 還沒有 new 完,所以 mSingleMode 還是為空的,所以B 也開始 new 出一個對象出來,這樣就相當(dāng)于創(chuàng)建了兩個實例了,所以,上面這種設(shè)計并不能保證線程安全。
2.1、如何實現(xiàn)懶漢式線程安全?
有人會說,簡單啊,你既然是線程并發(fā)不安全,那么加上一個 synchronized 線程鎖不就完事了?但是這樣以來,會降低整個訪問速度,而且每次都要判斷,這個真的是我們想要的嗎?
由于上面的缺點,所以,我們可以對上面的懶漢式加個優(yōu)化,如雙重檢查枷鎖:
public class SingleMode{
//構(gòu)造方法私有化,這樣外界就不能訪問了
private SingleMode(){
};
private static SingleMode mSingleMode;
public static SingleMode getInstance(){
if (mSingleMode == null){
synchronized (SingleMode.class){
if (mSingleMode == null){ //二次檢測
mSingleMode = new SingleMode();
}
}
}
return mSingleMode;
}
}
在上面的基礎(chǔ)上,用了二次檢查,這樣就保證了線程安全了,它會先判斷是否為null,是才會去加載,而且用 synchronized 修飾,則又保證了線程安全。
但是如果上面我們沒有用 volatile 修飾,它還是不安全的,有可能會出現(xiàn)null的問題。為什么?這是因為 java 在 new 一個對象的時候,它是無序的。而這個過程我們假設(shè)一下,假如有線程A,判斷為null了,這個時候它就進入線程鎖了 mSingleMode = new SingleMode();,它不是一蹴而就,而是需要3步來完成的。
- 1、為 mSingleMode 創(chuàng)建內(nèi)存
- 2、new SingleMode() 調(diào)用這個構(gòu)造方法
- 3、mSingleMode 指向內(nèi)存區(qū)域
那你可能會有疑問,這樣不是很正常嗎?怎么會有 null 的情況?
非也,java 虛擬機在執(zhí)行上面這三步的時候,并不是按照這樣的順序來的,可能會打亂,這兒就是java重排序,比如2和3調(diào)換一下:
- 1、為 mSingleMode 創(chuàng)建內(nèi)存
- 3、mSingleMode 指向內(nèi)存區(qū)域
- 2、new SingleMode() 調(diào)用這個構(gòu)造方法
那這個時候,mSingleMode 已經(jīng)指向內(nèi)存區(qū)域了,那這個時候它就不為 null了,而實際上它并未獲得構(gòu)造方法,比如構(gòu)造方面里面有些參數(shù)或者方法,但是你并未獲取,然而這個時候線程B過來,而 mSingleMode已經(jīng)指向內(nèi)存區(qū)域不為空了,但方法和參數(shù)并未獲得, 所以,這樣你線程B在執(zhí)行 mSingleMode 的某些方法時就會報錯。
當(dāng)然這種情況是非常少見的,不過還是暴露了這種問題所在。
所以我們用volatile 修飾,我們都知道 volatile 的一個重要屬性是可見性,即被 volatile 修飾的對象,在不同線程中是可以實時更新的,也是說線程A修改了某個被volatile修飾的值,那么我線程B也知道它被修改了。但它還有另一個作用就是禁止java重排序的作用,這樣我們就不用擔(dān)心出現(xiàn)上面這種null 的情況了。如下:
public class SingleMode{
//構(gòu)造方法私有化,這樣外界就不能訪問了
private SingleMode(){
};
private volatile static SingleMode mSingleMode;
public static SingleMode getInstance(){
if (mSingleMode == null){
synchronized (SingleMode.class){
if (mSingleMode == null){ //二次檢測
mSingleMode = new SingleMode();
}
}
}
return mSingleMode;
}
}
看到這里,是不是感覺爬了幾百盤的坑,終于上了黃金段位了。。。
然而,并不是,你打了排位之后發(fā)現(xiàn)還是被吊打,所以我們可能還忽略了什么。
沒錯,這種方式,依舊存在缺點:
由于volatile關(guān)鍵字會屏蔽會虛擬機中一些必要的代碼優(yōu)化,所以運行效率并不是很高。因此也建議,沒有特別的需要,不要大量使用。
筆者就遇到,使用這種模式,不知道什么原因,第二次進入 activity的時候,view 刷不出來,然而數(shù)據(jù)對象什么的都存在,調(diào)得我心力交瘁,欲生欲死,最后換了其他單例模式就ok了,希望懂的大俠告訴我一下,我只能懷疑volatile了。。。。。。
那你都這樣說了,那還怎么玩,有沒有一種更好的方式呢?別急,往下看。
3、靜態(tài)式
什么叫靜態(tài)式呢?回顧一下上面的餓漢式,我們再剛開始的就初始化了,不管你需不需要,而我們也說過,Java 再裝載類的時候,是不會并發(fā)的,那么,我們能不能zuo做到懶加載,即需要的時候再去初始化,又能保證線程安全呢?當(dāng)然可以,如下:
public class SingleMode{
//構(gòu)造方法私有化,這樣外界就不能訪問了
private SingleMode(){
};
public static class Holder{
private static SingleMode mSingleMode = new SingleMode();
public static SingleMode getInstance(){
return mSingleMode;
}
}
}
除了上面的餓漢式和懶漢式,,靜態(tài)的好處在于能保證線程安全,不用去考慮太多、缺點就在于對參數(shù)的傳遞比較不好。
那么這個時候,問題來了,參數(shù)怎么傳遞?這個確實沒懶漢式方便,不過沒關(guān)系,我們可以定義一個init()就可以了,只不過初始化的時候多了一行代碼;如:
public class SingleMode {
//構(gòu)造方法私有化,這樣外界就不能訪問了
private SingleMode(){
};
public static class Holder{
private static SingleMode mSingleMode = new SingleMode();
public static SingleMode getInstance(){
return mSingleMode;
}
}
private Context mContext;
public void init(Context context){
this.mContext = context;
}
}
初始化:
SingleMode mSingleMode = SingleMode.Holder.getInstance();
mSingleMode.init(this);
4、枚舉單例
java 1.4 之前,我們習(xí)慣用靜態(tài)內(nèi)部類的方式來實現(xiàn)單例模式,但在1.5之后,在 《Effective java》也提到了這個觀點,使用枚舉的優(yōu)點如下:
- 線程安全
- 延時加載
- 序列化和反序列化安全
所以,現(xiàn)在一般用單個枚舉的方式來實現(xiàn)單例,如上面,我們改一下:
public static SingleMode getInstance(){
return Singleton.SINGLETON.getSingleTon();
}
public enum Singleton{
SINGLETON ; //枚舉本身序列化之后返回的實例,名字隨便取
private AppUninstallModel singleton;
Singleton(){ //JVM保證只實例一次
singleton = new AppUninstallModel();
}
// 公布對外方法
public SingleMode getSingleTon(){
return singleton;
}
}
好吧,這樣就ok了,但還是那個問題,初始化參數(shù)跟靜態(tài)類一樣,還是得重新寫個 init() 有失必有得吧。
這樣,我們的單例模式就學(xué)完了。