溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

【設(shè)計(jì)模式與Android】單例模式——獨(dú)一無(wú)二的皇帝

發(fā)布時(shí)間:2020-07-16 20:12:46 來(lái)源:網(wǎng)絡(luò) 閱讀:1361 作者:東風(fēng)玖哥 欄目:移動(dòng)開(kāi)發(fā)

什么是單例模式

 

所謂單例模式,就是確保某一個(gè)類(lèi)只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例的設(shè)計(jì)模式。單例模式是最簡(jiǎn)單的設(shè)計(jì)模式,也是應(yīng)用最廣的設(shè)計(jì)模式。一般用于避免產(chǎn)生多個(gè)對(duì)象消耗過(guò)多的資源或者某種類(lèi)型的對(duì)象必須獨(dú)一無(wú)二的情景。

 

單例模式的實(shí)現(xiàn)方式

 

(1)餓漢式

單例模式極其簡(jiǎn)單,僅有一個(gè)單例類(lèi)。既然常用于確保某種類(lèi)型的對(duì)象必須獨(dú)一無(wú)二的情景,那么我們可以用皇帝來(lái)舉例。代碼如下:

public class Emperor {

    
//初始化一個(gè)皇帝,國(guó)不可一日無(wú)君
    private static final Emperor emperor = new Emperor();

    
//防止刁民冒充皇帝
    private Emperor(){}

    
public static Emperor getEmperor(){
        
return emperor;
    }

}

 

從上述代碼中可以看到,類(lèi)不能通過(guò)new的形式構(gòu)造對(duì)象,只能用方法來(lái)獲取唯一的靜態(tài)對(duì)象。這種在聲明的時(shí)候就初始化的實(shí)現(xiàn)方式就叫做餓漢式。

 

2)懶漢式

與餓漢式不同,懶漢式只有在第一次調(diào)用方式時(shí)才進(jìn)行初始化。實(shí)現(xiàn)代碼如下:

public class Singleton {
    
    
private static Singleton instance;
    
    
private Singleton(){}
    
    
public static synchronized Singleton getInstance(){
        
if (instance == null){
            
instance = new Singleton();
        }
        
return instance;
    }
    
}

 

懶漢式在方法中添加了synchronized關(guān)鍵字,可以在多線程情況下確保單例對(duì)象獨(dú)一無(wú)二。但即使已經(jīng)被初始化,每次調(diào)用還會(huì)進(jìn)行同步,會(huì)消耗不必要的資源,并且第一次加載時(shí)進(jìn)行實(shí)例化會(huì)拖慢反應(yīng)速度,因此懶漢式一般不建議使用。但懶漢式并非一無(wú)是處,如果一直沒(méi)有人用的話,就不會(huì)創(chuàng)建實(shí)例,則是節(jié)約空間。這是以時(shí)間換空間的實(shí)現(xiàn)方式,與餓漢式的以空間換時(shí)間各有所長(zhǎng)。

 

還記得語(yǔ)文老師講課外文學(xué)知識(shí)題的“禪杖就是魯智深,戒刀就是武松,板斧就是李逵”的規(guī)律嗎?注意上面代碼的getIntance()方法,實(shí)戰(zhàn)中見(jiàn)到getIntance()就是單例模式的準(zhǔn)確率八九不離十。

 

3)DCL式

Double Check Lock(以下簡(jiǎn)稱(chēng)DLC)實(shí)現(xiàn)單例模式既能夠在需要時(shí)才初始化對(duì)象,又能保證線程安全。代碼如下:

public class Singleton {

    
private static Singleton instance;

    
private Singleton(){}

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

}

 

DCL可以保證無(wú)論何時(shí)讀取這個(gè)變量,都是讀到內(nèi)存中最新的值,無(wú)論何時(shí)寫(xiě)這個(gè)變量,都可以立即寫(xiě)到內(nèi)存中。DCL是目前單例模式最常見(jiàn)的實(shí)現(xiàn)方式。

4)靜態(tài)內(nèi)部類(lèi)式

DCL盡管能完美解決資源消耗、同步多余、線程不安全的問(wèn)題,卻有低概率在并發(fā)場(chǎng)景比較復(fù)雜的情況下失效(少見(jiàn)于J2EE和Hadoop等場(chǎng)景,絕少見(jiàn)于Android場(chǎng)景)。因此在對(duì)性能要求極高的情況下我們可以采取靜態(tài)內(nèi)部類(lèi)式來(lái)實(shí)現(xiàn)單例模式。代碼如下:

public class Singleton {

    
private Singleton(){}

    
public static Singleton getInstance(){
        
return SingletonHolder.instance;
    }
    
    
private static class SingletonHolder{
        
private static final Singleton instance = new Singleton();
    }

}

 

 

靜態(tài)內(nèi)部類(lèi)式利用ClassLoader機(jī)制來(lái)保證初始化時(shí)僅有一個(gè)線程,不但不會(huì)造成性能損耗,還是天衣無(wú)縫的安全方式。

 

(5)單例模式的容器式管理

還是拿皇帝舉例子,天下可能有多個(gè)皇帝,一個(gè)軟件也可能有多個(gè)單例對(duì)象。舉唐玄宗李隆基和大燕皇帝安祿山的對(duì)立的例子不是太恰當(dāng),畢竟幾乎沒(méi)有史書(shū)認(rèn)同安祿山是合法皇帝。我就舉一個(gè)大家耳熟能詳?shù)睦?/span>——《三國(guó)演義》第80回講述了中國(guó)歷史上第一次同時(shí)存在兩位被后世的歷史學(xué)家認(rèn)定為合法皇帝(曹丕和劉備)的局面,也是最著名的一次。我們先建立一個(gè)管理類(lèi):

public class EmperorManager {

    
public static final String WEI = "魏";
    
public static final String SHU = "蜀";
    
public static final String WU = "吳";
    
    
private static Map<String,Object> emperors = new HashMap<>();

    
private EmperorManager(){}

    
public static void ascendEmperor(String key,Object emperor){
        
if (!emperors.containsKey(key)){
            
emperors.put(key,emperor);
        }
    }
    
    
public static Object getEmperor(String key){
        
return emperors.get(key);
    }

}

然后就可以管理多個(gè)單例對(duì)象了:

//曹丕廢帝篡炎劉
EmperorManager.ascendEmperor(EmperorManager.WEI,WeiEmperor.getEmperor());
//漢王正位續(xù)大統(tǒng)
EmperorManager.ascendEmperor(EmperorManager.SHU,ShuEmperor.getEmperor());

幾年后,孫權(quán)登基,天下又有了新的皇帝,寫(xiě)法以此類(lèi)推。

 

Android源碼中的單例模式

 

1)Application

Application是Android中最典型,也是最常見(jiàn)的單例模式。用戶重寫(xiě)Application類(lèi)也只重寫(xiě)一個(gè)。

 

2)Activity

Activity在singleInstance啟動(dòng)模式下只有一個(gè)實(shí)例,并且這個(gè)實(shí)例獨(dú)立運(yùn)行在一個(gè)Task中,不允許有別的Activity存在,這也是一種單例模式。

 

3)Service

Service用bindService()啟動(dòng)之后,無(wú)論再啟動(dòng)多少次,都只會(huì)調(diào)用onStartCommand()而不會(huì)再調(diào)用onCreate(),因?yàn)槊看握{(diào)用的Service都是同一對(duì)象。

 

4)各種Manager

Android中有很多管理類(lèi),比如WindowManager、PowerManager、SensorManager、ActivityManager、StorageManager以及ServiceManager等等,這些管理類(lèi)分別對(duì)某些資源進(jìn)行操作,為了避免對(duì)同一資源的同時(shí)操作,也為了節(jié)約資源,都采取了單例模式。

 

5)UID

Picasso和Glide等框架流行起來(lái)之前,最常見(jiàn)的圖片加載框架非Universal-Image-Loader(以下簡(jiǎn)稱(chēng)UID)莫屬,UID的初始化方式如下:

//全局初始化此配置
ImageLoader.getInstance().init(config);

 

這里又見(jiàn)到了熟悉的getIntance(),根據(jù)思維定式判定這是單例模式。

 

Android開(kāi)發(fā)中如何利用單例模式

 

1)當(dāng)創(chuàng)建一個(gè)對(duì)象需要較多資源時(shí),比如讀取配置或依賴(lài)較多其他對(duì)象時(shí),可以用創(chuàng)建一個(gè)單例對(duì)象常駐內(nèi)存的方式解決這個(gè)問(wèn)題。

 

2)當(dāng)一個(gè)對(duì)象需要經(jīng)常調(diào)用所以需要反復(fù)創(chuàng)建、銷(xiāo)毀時(shí),為了減少內(nèi)存開(kāi)支,可以用單例模式來(lái)減少創(chuàng)建、銷(xiāo)毀該對(duì)象的資源浪費(fèi)。

 

3)當(dāng)需要對(duì)同一個(gè)資源進(jìn)行操作時(shí)(例如File I/O),可以創(chuàng)建一個(gè)FileManager,這樣內(nèi)存里只有一個(gè)實(shí)例,避免了對(duì)同一個(gè)資源的同時(shí)操作。

 

需要注意的幾個(gè)問(wèn)題

 

1)單例模式必然有static修飾符,如果持有Activity的Context,很容易造成OOM,因此盡量使用Application的Context;此外有多少初學(xué)者在Activity銷(xiāo)毀時(shí)忘記銷(xiāo)毀視頻或地圖的單例對(duì)象而吃了大虧?

 

2)在不需要獨(dú)一無(wú)二的對(duì)象的時(shí)候不要采用單例模式,譬如自定義控件就是最不適合單例模式的場(chǎng)景。


本系列其他博客

 

【設(shè)計(jì)模式與Android】建造者模式——建軍大業(yè)


【設(shè)計(jì)模式與Android】原型模式——復(fù)制中心走出來(lái)的克隆人


【設(shè)計(jì)模式與Android】工廠方法模式——化工女神的工廠


【設(shè)計(jì)模式與Android】抽象工廠模式——嵌合體克隆工廠


【設(shè)計(jì)模式與Android】策略模式——錦囊里的上策中策下策



向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI