[Android]《Android 源碼設計模式解析與實戰》讀書筆記 3

簡介

這周繼續寫《Android源碼設計模式解析與實戰》讀書筆記。本書的第二章介紹了單例模式的各種實現方式,以及在 Android 源碼中的應用。

單例模式介紹

確保某一個類只有一個實例,而且自行實例化并向整個系統提供這個實例。它的作用是避免產生多個對象消耗過多的資源,或者某種類型的對象只應該有且只有一個。比如創建一個對象需要消耗的資源過多,如要訪問 IO 和數據庫等資源。

單例模式使用要點

單例模式 UML 類圖如下:

單例模式 UML 類圖

實現單例模式主要有如下幾個關鍵點:

1.構造函數不對外開放,一般為 Private;

2.通過一個靜態方法或者枚舉返回單例對象;

3.確保單例類的對象只有一個,尤其是在多線程環境下(難點);

4.確保單例類對象在反序列化時不會重新構建對象。

單例模式用法

餓漢模式

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

餓漢模式在裝載類時就創建對象實例,是典型的空間換時間。

懶漢模式

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

懶漢模式在每次獲取實例時都會進行判斷,是典型的時間換空間。 getInstance() 方法中添加了 synchronized關鍵字,也就是上面所說的在多線程情況下保證單例對象唯一性的手段。但是即使 instance 已經被初始化,每次調用 getInstance() 方法都會進行同步,浪費不必要的資源,這也就是懶漢模式的最大問題。因此這種模式一般不建議使用。

雙重檢查鎖定(Double Check Lock)實現單例

public class DCLSingleton {
    // JDK1.5后的版本才可使用volatile關鍵字,保證sInstance對象每次都從主內存中讀取
    private volatile static DCLSingleton sInstance = null;

    private DCLSingleton() {
    }
    
    public static DCLSingleton getInstance(){
        if(sInstance==null){
            synchronized(DCLSingleton.class){
                if(sInstance==null){
                    sInstance=new DCLSingleton();
                }
            }
        }
        return sInstance;
    }
}

這個寫法的特別之處在于對 instance 進行了兩次判空:第一層主要是為了避免不必要的同步,第二層則是為了在 null 的情況下創建實例。
我們會發現上面代碼有一個volatile關鍵字,因為在這里會有DCL失效問題。

DCL 失效問題:假設線程 A 執行到sInstance=new DCLSingleton()語句,這看上去像是一句代碼,實際上它并不是一個原子操作,這句代碼最終會被編譯為多條匯編指令,它大致做了三件事:

1.給 sInstance 的實例分配內存;

2.調用 DCLSingleton 的構造函數,初始化成員字段;

3.將 sInstance 對象指向分配的內存空間(此時 sInstance 就不是 null了)。

但是由于 Java 編譯器允許處理器亂序執行。因此執行順序可能是 1-2-3 也可能是 1-3-2,如果是后者,并且在 3 執行完畢、2 未執行之前被切換到 B 線程上,這時的 sInstance 因為已經在線程 A 內執行過了第三點,sInstance 已經是非空了,所以線程 B 直接取走 sInstance,再使用就會出錯,這就是 DCL 失效問題。

JDK 1.5 之后的版本具體化了 volatile 關鍵字,用它可以保證 sInstance 對象每次都從主內存中讀取,雖然會影響性能,這種方式第一次加載時會稍慢,在高并發環境會有缺陷,但是一般能夠滿足需求。

靜態內部類單例模式

public class Singleton implements  Serializable{
    private Singleton(){}
    
    public static Singleton getInstance(){
        return SingletonHolder.sInstance;
    }
    /**
     * 靜態內部類
     */
    private static class SingletonHolder{
        private static final Singleton sInstance=new Singleton();
    }
    
    /**
     * 為了杜絕對象在反序列化時重新生成對象,則重寫Serializable的私有方法
     * @return
     * @throws ObjectStreamException
     */
    private Object readResolve() throws ObjectStreamException{
        return SingletonHolder.sInstance;
    }
    
}

這種是推薦使用的單例模式實現方式。當第一次加載Singleton類時并不會初始化INSTANCE,只有在第一次調用getInstance方法時才會導致INSTANCE被初始化。這種方式不僅能夠保證線程安全,也能保證單例對象的唯一性,同時也延長了單例的實例化。
上面的代碼重寫了 readResolve() 方法,這是因為通過序列化可以將一個單例的實例對象寫到磁盤,然后讀回來,從而獲得一個實例。即使構造函數是私有的,反序列化時依然可以通過特殊的途徑去創建類的一個新的實例,相當于調用該類的構造函數。反序列化操作提供了一個特別的鉤子函數,類中具有一個私有的、被實例化的方法 readResolve(),這個方法可以讓開發人員控制對象的反序列化。重寫該方法返回 SingletonHolder.sInstance ,而不是默認的生成新的實例,從而保持單例。

枚舉單例

public enum SingletonEnum {
    INSTANCE;
    public void doSomething(){
        System.out.println("do sth.");
    }
}

這種方式是Effective Java作者Josh Bloch 提倡的方式,它不僅能避免多線程同步問題,而且還能防止反序列化重新創建新的對象。

容器實現單例

public class SingletonManager {
    private static Map<String,Object> objMap=new HashMap<String,Object>();
    
    private SingletonManager(){};
    
    public static void registerService(String key,Object instance){
        if(!objMap.containsKey(key)){
            objMap.put(key, instance);
        }
    }
    
    public static Object getService(String key){
        return objMap.get(key);
    }
}

將多種單例類型注入到一個統一的管理類中,在使用時根據key獲取對象對應類型的對象。這種方式使得我們可以管理多種類型的單例,并且在使用時可以通過統一的接口進行獲取操作,降低了用戶的使用成本,也對用戶隱藏了具體實現,降低了耦合度。

單例模式運用場景

  1. Windows 的 Task Manager (任務管理器)就是很典型的單例模式(這個很熟悉吧),想想看,是不是呢,你能打開兩個 windows task manager 嗎? 不信你自己試試看哦~

  2. windows的Recycle Bin(回收站)也是典型的單例應用。在整個系統運行過程中,回收站一直維護著僅有的一個實例。

  3. 網站的計數器,一般也是采用單例模式實現,否則難以同步。

  4. 應用程序的日志應用,一般都何用單例模式實現,這一般是由于共享的日志文件一直處于打開狀態,因為只能有一個實例去操作,否則內容不好追加。

  5. Web 應用的配置對象的讀取,一般也應用單例模式,這個是由于配置文件是共享的資源。

  6. 數據庫連接池的設計一般也是采用單例模式,因為數據庫連接是一種數據庫資源。數據庫軟件系統中使用數據庫連接池,主要是節省打開或者關閉數據庫連接所引起的效率損耗,這種效率上的損耗還是非常昂貴的,因為何用單例模式來維護,就可以大大降低這種損耗。

  7. 多線程的線程池的設計一般也是采用單例模式,這是由于線程池要方便對池中的線程進行控制。

  8. 操作系統的文件系統,也是大的單例模式實現的具體例子,一個操作系統只能有一個文件系統。

  9. HttpApplication 也是單位例的典型應用。熟悉 ASP.Net(IIS) 的整個請求生命周期的人應該知道 HttpApplication 也是單例模式,所有的 HttpModule 都共享一個 HttpApplication 實例.

總結以上,不難看出:

單例模式應用的場景一般發現在以下條件下:

(1)資源共享的情況下,避免由于資源操作時導致的性能或損耗等。如上述中的日志文件,應用配置。

(2)控制資源的情況下,方便資源之間的互相通信。如線程池等。

Android源碼中的單例模式

在 Android 系統中,我們經常會通過 Context 獲取系統級別的服務,如 WindowsManagerService、ActivityManagerService 等,更常用的是一個 LayoutInflater 的類,這些服務會在合適的時候以單例的形式注冊在系統中,在我們需要的時候就通過 Context 的 getSystemService(String name) 獲取。

總結

優點:

1.由于單例模式在內存中只有一個實例,減少了內存開支,特別是一個對象需要頻繁的創建、銷毀時,而且創建或銷毀時性能又無法優化,單例模式的優勢就非常明顯。

2.單例模式可以避免對資源的多重占用,例如一個文件操作,由于只有一個實例存在內存中,避免對同一資源文件的同時操作。

3.單例模式可以在系統設置全局的訪問點,優化和共享資源訪問,例如,可以設計一個單例類,負責所有數據表的映射處理。

缺點:

1.單例模式一般沒有接口,擴展很困難,若要擴展,只能修改代碼來實現。

2.單例對象如果持有 Context,那么很容易引發內存泄露。此時需要注意傳遞給單例對象的 Context 最好是 Application Context。

參考資料

設計模式之——單例模式(Singleton)的常見應用場景

《Android 源碼設計模式解析與實戰 》

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,732評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,214評論 3 426
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,781評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,588評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,315評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,699評論 1 327
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,698評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,882評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,441評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,189評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,388評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,933評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,613評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,023評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,310評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,112評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,334評論 2 377

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,702評論 25 708
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,816評論 18 139
  • 介紹 單例對象的類必須保證只有一個實例存在。某個類只有一個實例,而且自行實例化并向整個系統提供這個實例。 場景 避...
    appcompat_v7閱讀 228評論 0 1
  • 我想幫幫她。 她說催回家的電話不會打,短信居然也發不出。寫不出來。寫了刪,刪了寫,終又字字撤回。為什么怎么寫都感覺...
    斑錦閱讀 498評論 16 12
  • 內心苦悶煩惱,很期待一次旅行,于是便開始各種糾結,一如往常,去哪兒,怎么去,花多少錢,住哪里,猶猶豫豫反反復復,真...
    倏忽而至閱讀 164評論 0 0