Android 常用設計模式(二) -- 單例模式(詳解)

作者 : 夏至 歡迎轉(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() 有失必有得吧奄薇。

這樣,我們的單例模式就學完了事格。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末惕艳,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子驹愚,更是在濱河造成了極大的恐慌,老刑警劉巖劣纲,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逢捺,死亡現(xiàn)場離奇詭異,居然都是意外死亡癞季,警方通過查閱死者的電腦和手機劫瞳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绷柒,“玉大人志于,你說我怎么就攤上這事》夏溃” “怎么了伺绽?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我奈应,道長澜掩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任杖挣,我火速辦了婚禮肩榕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘惩妇。我一直安慰自己株汉,他們只是感情好,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布歌殃。 她就那樣靜靜地躺著郎逃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪挺份。 梳的紋絲不亂的頭發(fā)上褒翰,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機與錄音匀泊,去河邊找鬼优训。 笑死,一個胖子當著我的面吹牛各聘,可吹牛的內(nèi)容都是我干的揣非。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼躲因,長吁一口氣:“原來是場噩夢啊……” “哼早敬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起大脉,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤搞监,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后镰矿,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體琐驴,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年秤标,在試婚紗的時候發(fā)現(xiàn)自己被綠了绝淡。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡苍姜,死狀恐怖牢酵,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情衙猪,我是刑警寧澤馍乙,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布布近,位于F島的核電站,受9級特大地震影響潘拨,放射性物質(zhì)發(fā)生泄漏吊输。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一铁追、第九天 我趴在偏房一處隱蔽的房頂上張望季蚂。 院中可真熱鬧,春花似錦琅束、人聲如沸扭屁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽料滥。三九已至,卻和暖如春艾船,著一層夾襖步出監(jiān)牢的瞬間葵腹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工屿岂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留践宴,地道東北人。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓爷怀,卻偏偏與公主長得像阻肩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子运授,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 單例模式(SingletonPattern)一般被認為是最簡單烤惊、最易理解的設計模式,也因為它的簡潔易懂吁朦,是項目中最...
    成熱了閱讀 4,255評論 4 34
  • 一個簡單的單例示例 單例模式可能是大家經(jīng)常接觸和使用的一個設計模式柒室,你可能會這么寫 publicclassUnsa...
    Martin說閱讀 2,230評論 0 6
  • 1 單例模式的動機 對于一個軟件系統(tǒng)的某些類而言,我們無須創(chuàng)建多個實例喇完。舉個大家都熟知的例子——Windows任務...
    justCode_閱讀 1,433評論 2 9
  • 從三月份找實習到現(xiàn)在伦泥,面了一些公司,掛了不少锦溪,但最終還是拿到小米、百度府怯、阿里刻诊、京東、新浪牺丙、CVTE则涯、樂視家的研發(fā)崗...
    時芥藍閱讀 42,271評論 11 349
  • 一片干旱的荒原复局,因被人們過度開墾而荒蕪已久。 荒原上有一棵突兀的小樹粟判,干干瘦瘦的亿昏。在他的不遠處有些不知名的野花野草...
    Ai慢慢講故事閱讀 539評論 1 1