作者 : 夏至 歡迎轉(zhuǎn)載夭委,也請保留這段申明
http://blog.csdn.net/u011418943/article/details/60139644
上一篇講到策略模式,變動的代碼需要用到策略模式由驹,感興趣的小伙伴可以看看.
傳送門:Android 常用設計模式之 -- 策略模式
單例模式的定義就不解釋過多了,相信很多小伙伴在設計的時候,都用到這個模式;常用的場景為 數(shù)據(jù)庫的訪問猜嘱,文件流的訪問以及網(wǎng)絡連接池的訪問等等,在這些場景中嫁艇,我們都希望實例只有一個朗伶,除了減少內(nèi)存開銷之外,也防止防止多進程修改文件錯亂和數(shù)據(jù)庫鎖住的問題裳仆。
在這一篇文章中腕让,我將帶你分析 android 常見的集中單例模式,并詳細分析他們的優(yōu)缺點歧斟。讓大家在以后的選擇單例中纯丸,可以根據(jù)實際情況選擇。
當然静袖,如有錯誤觉鼻,也歡迎指正。
下面是介紹:
1队橙、餓漢式
就是初始化的時候坠陈,直接初始化,典型的以時間換空間的做法捐康。
public class SingleMode{
//構(gòu)造方法私有化仇矾,這樣外界就不能訪問了
private SingleMode(){
};
//當類被初始化的時候,就直接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)點了
缺點:
單線程只實例了一次,如果是多線程了邻吞,那么它會被多次實例
至于問什么說它是線程不安全的呢组题?先下面這張圖:
我們假設一下,有兩個線程抱冷,A和B都要初始化這個實例崔列;此時 A 比較快,已經(jīng)判斷 mSingleMode 為null旺遮,正在創(chuàng)建實例赵讯,而 B 這時候也再判斷,但此時 A 還沒有 new 完耿眉,所以 mSingleMode 還是為空的边翼,所以B 也開始 new 出一個對象出來,這樣就相當于創(chuàng)建了兩個實例了鸣剪,所以组底,上面這種設計并不能保證線程安全。
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 一個對象的時候鲁森,它是無序的祟滴。而這個過程我們假設一下,假如有線程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 的某些方法時就會報錯唇撬。
當然這種情況是非常少見的,不過還是暴露了這種問題所在展融。
所以我們用volatile 修飾窖认,我們都知道 volatile 的一個重要屬性是可見性,即被 volatile 修飾的對象愈污,在不同線程中是可以實時更新的耀态,也是說線程A修改了某個被volatile修飾的值轮傍,那么我線程B也知道它被修改了暂雹。但它還有另一個作用就是禁止java重排序的作用,這樣我們就不用擔心出現(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做到懶加載,即需要的時候再去初始化赊颠,又能保證線程安全呢格二?當然可以,如下:
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 之前,我們習慣用靜態(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() 有失必有得吧奄薇。
這樣,我們的單例模式就學完了事格。