Android設(shè)計(jì)模式-單例

一直想整理一下關(guān)于andorid設(shè)計(jì)模式的文章,也方便自己需要的時(shí)候回顧下,由于有些地方也沒弄明白雇盖,所以有些地方可能理解的不對,對于這些歡迎大家指出栖忠,共同討論一起進(jìn)步崔挖,@—@ 哈哈!

單例模式:

單例模式應(yīng)該是設(shè)計(jì)模式中最容易理解庵寞,也是最容易手寫代碼的狸相,大家平時(shí)寫代碼中也應(yīng)該經(jīng)常使用到,那究竟什么是單例了? 我的理解就是在一個(gè)類中只實(shí)例化一個(gè)實(shí)例捐川,并向整個(gè)系統(tǒng)提供一個(gè)節(jié)點(diǎn)使用該個(gè)實(shí)例脓鹃。那什么時(shí)候應(yīng)該去使用單例模式了?通常來說只有是在該類需要進(jìn)行大量耗資源操作或者只需要?jiǎng)?chuàng)建一個(gè)實(shí)例的情況下使用属拾,例如一個(gè)班級只有一個(gè)班長将谊,一個(gè)國家只有一個(gè)主席等等。而在android系統(tǒng)中也有很多地方用到單例模式渐白,我們經(jīng)常會(huì)通過Context獲取系統(tǒng)級別的服務(wù)尊浓,比如WindowsManagerService, ActivityManagerService等,更常用的是一個(gè)叫LayoutInflater的類, 這些類以單例的形式注冊在系統(tǒng)中纯衍,我們需要的時(shí)候就通過Context的getSystemService(String key)獲取栋齿。

既然說了什么是單例,那如何使用了?單例的寫法在java中有很多瓦堵,接下來回列舉一些常用的寫法基协。

1.懶漢式(就是在需要調(diào)用時(shí)候去加載)
public class SingleInstance {

private static SingleInstance mInstance;

/**
 * 創(chuàng)建私有構(gòu)造器
 */
private SingleInstance() {

}

public static SingleInstance getInstance() {
    if (mInstance == null) {
        mInstance = new SingleInstance();
    }
    return mInstance;
}}

這個(gè)應(yīng)該是最常見的一種寫法,很多書上視頻上都是這樣寫的菇用,但是這種寫法在多線程的情況下會(huì)實(shí)例化多個(gè)實(shí)例澜驮,因此這個(gè)是線程不安全的。所以為了保證線程安全可以加上關(guān)鍵字synchronized

public class SingleInstance {

private static SingleInstance mInstance;

/**
 * 創(chuàng)建私有構(gòu)造器
 */
private SingleInstance() {

}

public static synchronized SingleInstance getInstance() {
    if (mInstance == null) {
        mInstance = new SingleInstance();
    }
    return mInstance;
}}

加上synchronized的關(guān)鍵字可以解決實(shí)例化多個(gè)實(shí)例的問題惋鸥,但方法前加這個(gè)效率并不高杂穷,每次只能用一個(gè)線程訪問,所以可以在上面的基礎(chǔ)上這樣修改卦绣。

2.雙重檢驗(yàn)鎖模式
public class SingleInstance {

private static SingleInstance mInstance;

/**
 * 創(chuàng)建私有構(gòu)造器
 */
private SingleInstance() {

}

public static SingleInstance getInstance() {
    if (mInstance == null) {
        synchronized (SingleInstance.class) {
            if(mInstance == null){
                mInstance = new SingleInstance();
            }
        }
    }
    return mInstance;
}}

這種寫法就是雙重檢驗(yàn)鎖模式耐量,進(jìn)行兩次檢驗(yàn)mInstance的操作,一次是同步前滤港,一次是同步后廊蜒,防止多次實(shí)例化。但是這種方式任然存在問題的可能溅漾,它是有問題山叮,主要在于mInstance= new SingleInstance()這句,這并非是一個(gè)原子操作樟凄,事實(shí)上在 JVM 中這句話大概做了下面 3 件事情聘芜。
1.給 mInstance分配內(nèi)存
2.調(diào)用 SingleInstance的構(gòu)造函數(shù)來初始化成員變量
3.將mInstance對象指向分配的內(nèi)存空間.
但是在 JVM 的即時(shí)編譯器中存在指令重排序的優(yōu)化。也就是說上面的第二步和第三步的順序是不能保證的缝龄,最終的執(zhí)行順序可能是 1-2-3 也可能是 1-3-2汰现。如果是后者,則在 3 執(zhí)行完畢叔壤、2 未執(zhí)行之前瞎饲,被線程二搶占了,這時(shí) mInstance已經(jīng)是非 null 了(但卻沒有初始化)炼绘,所以線程二會(huì)直接返回 mInstance,此時(shí)就會(huì)出錯(cuò)了嗅战。那對于上面這個(gè)問題我們只需要將 mInstance變量聲明成 volatile 就可以了。java 1.5后對其進(jìn)行了修改俺亮,當(dāng)引入volatile時(shí) 具備以下幾點(diǎn):

1)保證了不同線程對這個(gè)變量進(jìn)行操作時(shí)的可見性驮捍,即一個(gè)線程修改了某個(gè)變量的值,這新值對其他線程來說是立即可見的脚曾。
2)禁止進(jìn)行指令重排序东且。
volatile關(guān)鍵字禁止指令重排序有兩層意思:
  1)當(dāng)程序執(zhí)行到volatile變量的讀操作或者寫操作時(shí),在其前面的操作的更改肯定全部已經(jīng)進(jìn)行本讥,且結(jié)果已經(jīng)對后面的操作可見珊泳;在其后面的操作肯定還沒有進(jìn)行鲁冯;
  2)在進(jìn)行指令優(yōu)化時(shí),不能將在對volatile變量訪問的語句放在其后面執(zhí)行色查,也不能把volatile變量后面的語句放到其前面執(zhí)行薯演。
這樣就不會(huì)存在無序?qū)懭耄敲碨ingleInstace對象不會(huì)存在返回一個(gè)沒有實(shí)例話的對象秧了,只有實(shí)話成功后才會(huì)將該地址指向?qū)ο蟆?br> 具體vollatile可以參見這篇文章:Volatile跨扮。

3.餓漢式 static final field

這種方式是直接在類加載到內(nèi)存時(shí)去初始化的,同時(shí)聲明為final和static所以創(chuàng)建實(shí)例本身是線程安全的示惊。

public class SingleInstance {

//類加載時(shí)就初始化
private static final SingleInstance mInstance= new SingleInstance ();

private SingleInstance (){}

public static SingleInstance getInstance(){
    return mInstance;
}}
4.靜態(tài)內(nèi)部類 static nested class
public class SingleInstance {

private static class SingletonHolder {  

private static final Singleton INSTANCE = new Singleton();

 } 
private SingleInstance (){}

public final  static SingleInstance getInstance(){
    return mInstance;
}}

因?yàn)橥瑯有揎椓薴inal好港,static ,同時(shí)SingletonHolder是靜態(tài)內(nèi)部類米罚,只有g(shù)etInstance調(diào)用時(shí)才會(huì)加載,所以也是惰性初始化(懶加載)丈探。

5.最后一種用的很少的枚舉

用枚舉寫單例實(shí)在太簡單了录择!這也是它最大的優(yōu)點(diǎn)。下面這段代碼就是聲明枚舉實(shí)例的通常做法碗降。

   public enum SingleInstance {

  //枚舉元素

  INSTANCE;}

而枚舉單例應(yīng)該是代碼最簡單的實(shí)現(xiàn)方式了隘竭,同時(shí)一般單例存在的另外一個(gè)問題是一旦你實(shí)現(xiàn)了序列化接口,那么它們不再保持單例了讼渊,因?yàn)閞eadObject()方法一直返回一個(gè)新的對象就动看,而枚舉你可以通過使用readResolve()方法來避免。

public enum SingleInstance {
    //枚舉元素
    private Object readResolve(){
        return INSTANCE;
    }
}

這些就是常用的單例實(shí)現(xiàn)方式爪幻,那么是不是保證單例的安全性和效率是不是就不會(huì)有問題了菱皆,不是由于單例模式對象是靜態(tài)的所以其生命周期比較長,容易造成內(nèi)存泄漏挨稿。特別是傳遞Context 對象時(shí)特別注意:

public class SingleInstance {

private static SingleInstance mInstance;

private Context mcContext;

/**
 * 創(chuàng)建私有構(gòu)造器
 */
private SingleInstance(Context c) {
    mcContext = c;
}

public static SingleInstance getInstance(Context c) {
    if (mInstance == null) {
        synchronized (SingleInstance.class) {
            if (mInstance == null) {
                mInstance = new SingleInstance(c);
            }
        }
    }
    return mInstance;
}}

如果傳遞的是一個(gè)activity的句柄時(shí)仇轻,如果該activity退出,系統(tǒng)GC的時(shí)候試圖去回收奶甘,但它的句柄被該單例對象引用篷店,GC回收它失敗,就會(huì)造成內(nèi)存泄漏臭家。所以單例的時(shí)候如果引入Context對象時(shí)疲陕,最好用弱引用或者private WeakReference<Context> c,或者是調(diào)用ApplicationContext對象其生命周期伴隨整個(gè)應(yīng)用钉赁。關(guān)于單例一般都是以上所說蹄殃,如果大家有更多的了解歡迎補(bǔ)充¢厦梗總的來說單例在某些情況下可控制實(shí)例產(chǎn)生的數(shù)量窃爷,達(dá)到節(jié)約資源的目的邑蒋,同時(shí)也可進(jìn)行數(shù)據(jù)媒介共享,它可以在不建立直接關(guān)聯(lián)的條件下按厘,讓多個(gè)不相關(guān)的兩個(gè)線程或者進(jìn)程之間實(shí)現(xiàn)通信医吊。但單例不足之處就是單利模式中沒有抽象層,因此單例類的擴(kuò)展有很大的困難逮京。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末卿堂,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子懒棉,更是在濱河造成了極大的恐慌草描,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件策严,死亡現(xiàn)場離奇詭異穗慕,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)妻导,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進(jìn)店門逛绵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人倔韭,你說我怎么就攤上這事术浪。” “怎么了寿酌?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵胰苏,是天一觀的道長。 經(jīng)常有香客問我醇疼,道長硕并,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任僵腺,我火速辦了婚禮鲤孵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘辰如。我一直安慰自己普监,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布琉兜。 她就那樣靜靜地躺著凯正,像睡著了一般。 火紅的嫁衣襯著肌膚如雪豌蟋。 梳的紋絲不亂的頭發(fā)上廊散,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天,我揣著相機(jī)與錄音梧疲,去河邊找鬼允睹。 笑死运准,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的缭受。 我是一名探鬼主播胁澳,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼米者!你這毒婦竟也來了韭畸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤蔓搞,失蹤者是張志新(化名)和其女友劉穎胰丁,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體喂分,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锦庸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了妻顶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片酸员。...
    茶點(diǎn)故事閱讀 38,814評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖讳嘱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情酿愧,我是刑警寧澤沥潭,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站嬉挡,受9級特大地震影響钝鸽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜庞钢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一拔恰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧基括,春花似錦颜懊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至桐款,卻和暖如春咸这,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背魔眨。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工媳维, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留酿雪,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓侄刽,卻偏偏與公主長得像指黎,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子唠梨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評論 2 351

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