天天看點

Android 設計模式 之 單例模式 2、android中源碼單例模式舉例

設計模式中,最簡單不過的就是單例模式。先看看單例模式

原文:http://www.iteye.com/topic/575052

singleton模式可以是很簡單的,它的全部隻需要一個類就可以完成(看看這章可憐的uml圖)。但是如果在“對象建立的次數以及何時被建立”這兩點上較真起來,singleton模式可以相當的複雜,比頭五種模式加起來還複雜,譬如涉及到dcl雙鎖檢測(double checked locking)的讨論、涉及到多個類加載器(classloader)協同時、涉及到跨jvm(叢集、遠端ejb等)時、涉及到單例對象被銷毀後重建等。

目的:

希望對象隻建立一個執行個體,并且提供一個全局的通路點。

Android 設計模式 之 單例模式 2、android中源碼單例模式舉例

圖6.1 單例模式的uml圖

結構是簡單的,但是卻存在一下情況;

1.每次從getinstance()都能傳回一個且唯一的一個對象。

2.資源共享情況下,getinstance()必須适應多線程并發通路。

3.提高通路性能。

4.懶加載(lazy load),在需要的時候才被構造。

[java] view

plaincopy

4.public class singletona {    

5.     

6.    /**  

7.     * 單例對象執行個體  

8.     */    

9.    private static singletona instance = null;    

10.     

11.    public static singletona getinstance() {    

12.        if (instance == null) {                              //line 12    

13.            instance = new singletona();          //line 13    

14.        }    

15.        return instance;    

16.    }    

17.}    

這個寫法我們把四點需求從上往下檢測,發現第2點的時候就出了問題,假設這樣的場景:兩個線程并發調用singleton.getinstance(),假設線程一先判斷完instance是否為null,既代碼中的line

12進入到line 13的位置。剛剛判斷完畢後,jvm将cpu資源切換給線程二,由于線程一還沒執行line 13,是以instance仍然是空的,是以線程二執行了new signleton()操作。片刻之後,線程一被重新喚醒,它執行的仍然是new signleton()操作。是以這種設計的單例模式不能滿足第2點需求。

下面我們繼續

4.public class singletonb {    

9.    private static singletonb instance = null;    

11.    public synchronized static singletonb getinstance() {    

12.        if (instance == null) {    

13.            instance = new singletonb();    

比起單例a僅僅在方法中多了一個synchronized修飾符,現在可以保證不會出線程問題了。但是這裡有個很大(至少耗時比例上很大)的性能問題。除了第一次調用時是執行了singletonkerriganb的構造函數之外,以後的每一次調用都是直接傳回instance對象。傳回對象這個操作耗時是很小的,絕大部分的耗時都用在synchronized修飾符的同步準備上,是以從性能上說很不劃算。

4.public class singletonc {    

9.    private static singletonkerrigand instance = null;    

11.    public static singletonc getinstance() {    

13.            synchronized (singletonc.class) {    

14.                if (instance == null) {    

15.                    instance = new singletonc();    

16.                }    

17.            }    

18.        }    

19.        return instance;    

20.    }    

21.}    

看起來這樣已經達到了我們的要求,除了第一次建立對象之外,其他的通路在第一個if中就傳回了,是以不會走到同步塊中。已經完美了嗎?

我們來看看這個場景:假設線程一執行到instance = new singletonkerrigand()這句,這裡看起來是一句話,但實際上它并不是一個原子操作(原子操作的意思就是這條語句要麼就被執行完,要麼就沒有被執行過,不能出現執行了一半這種情形)。事實上進階語言裡面非原子操作有很多,我們隻要看看這句話被編譯後在jvm執行的對應彙編代碼就發現,這句話被編譯成8條彙編指令,大緻做了3件事情:

1.給kerrigan的執行個體配置設定記憶體。

2.初始化kerrigan的構造器

3.将instance對象指向配置設定的記憶體空間(注意到這步instance就非null了)。

但是,由于java編譯器允許處理器亂序執行(out-of-order),以及jdk1.5之前jmm(java memory medel)中cache、寄存器到主記憶體回寫順序的規定,上面的第二點和第三點的順序是無法保證的,也就是說,執行順序可能是1-2-3也可能是1-3-2,如果是後者,并且在3執行完畢、2未執行之前,被切換到線程二上,這時候instance因為已經線上程一内執行過了第三點,instance已經是非空了,是以線程二直接拿走instance,然後使用,然後順理成章地報錯,而且這種難以跟蹤難以重制的錯誤估計調試上一星期都未必能找得出來,真是一茶幾的杯具啊。

dcl的寫法來實作單例是很多技術書、教科書(包括基于jdk1.4以前版本的書籍)上推薦的寫法,實際上是不完全正确的。的确在一些語言(譬如c語言)上dcl是可行的,取決于是否能保證2、3步的順序。在jdk1.5之後,官方已經注意到這種問題,是以調整了jmm、具體化了volatile關鍵字,是以如果jdk是1.5或之後的版本,隻需要将instance的定義改成“private volatile static singletonkerrigand

instance = null;”就可以保證每次都去instance都從主記憶體讀取,就可以使用dcl的寫法來完成單例模式。當然volatile或多或少也會影響到性能,最重要的是我們還要考慮jdk1.42以及之前的版本,是以本文中單例模式寫法的改進還在繼續。

代碼倒越來越複雜了,現在先來個返璞歸真,根據jls(java language specification)中的規定,一個類在一個classloader中隻會被初始化一次,這點是jvm本身保證的,那就把初始化執行個體的事情扔給jvm好了.

4.public class singletond {    

9.    private static singletond instance = new singletond();    

11.    public static singletond getinstance() {    

12.        return instance;    

13.    }    

14.}    

這種寫法不會出現并發問題,但是它是餓漢式的,在classloader加載類後kerrigan的執行個體就會第一時間被建立,餓漢式的建立方式在一些場景中将無法使用:譬如執行個體的建立是依賴參數或者配置檔案的,在getinstance()之前必須調用某個方法設定參數給它,那樣這種單例寫法就無法使用了。

可帶參數單例模式e:

4.public class singletone {    

6.    private static class singletonholder {    

7.        /**  

8.         * 單例對象執行個體  

9.         */    

10.        static final singletone instance = new singletone();    

11.    }    

12.     

13.    public static singletone getinstance() {    

14.        return singletonholder.instance;    

15.    }    

16.}    

這種寫法仍然使用jvm本身機制保證了線程安全問題;由于singletonholder是私有的,除了getinstance()之外沒有辦法通路它,是以它是懶漢式的;同時讀取執行個體的時候不會進行同步,沒有性能缺陷;也不依賴jdk版本。

當然,使用者以其它方式構造單例的對象,如果設計者不希望這樣的情況發生,則需要做規避措施。其它途徑建立單例執行個體的方式有:

1.直接new單例對象

2.通過反射構造單例對象

3.通過序列化構造單例對象。

對于第一種情況,一般我們會加入一個private或者protected的構造函數,這樣系統就不會自動添加那個public的構造函數了,是以隻能調用裡面的static方法,無法通過new建立對象。

對于第二種情況,反射時可以使用setaccessible方法來突破private的限制,我們需要做到第一點工作的同時,還需要在在 reflectpermission("suppressaccesschecks") 權限下使用安全管理器(securitymanager)的checkpermission方法來限制這種突破。一般來說,不會真的去做這些事情,都是通過應用伺服器進行背景配置實作。

對于第三種情況,如果單例對象有必要實作serializable接口(很少出現),則應當同時實作readresolve()方法來保證反序列化的時候得到原來的對象。

4.public class singletonf implements serializable {    

10.        static final singletonf instance = new singletonf();    

13.    public static singletonf getinstance() {    

16.     

17.    /**  

18.     * private的構造函數用于避免外界直接使用new來執行個體化對象  

19.     */    

20.    private singletonf() {    

21.    }    

22.     

23.    /**  

24.     * readresolve方法應對單例對象被序列化時候  

25.     */    

26.    private object readresolve() {    

27.        return getinstance();    

28.    }    

29.}    

app路徑:packages/providers/calendarprovider

檔案:packages/providers/calendarprovider/src/com/android/provider/calendar/calendardatabasehelper.java

單例代碼:

private static calendardatabasehelper ssingleton = null;      

public static synchronized calendardatabasehelper getinstance(context context) {  

        if (ssingleton == null) {  

            ssingleton = new calendardatabasehelper(context);  

        }  

        return ssingleton;  

    }  

可以看出,這是用到了2中的單例模式b.

檔案:libcore/luni/src/main/java/com/ibm/icu4jni/text/callator.java

libcore/luni/src/main/java/com/ibm/icu4jni/text/rulebasedcallator.java

public static collator getinstance(locale locale) {  

    return new rulebasedcollator(locale);  

}  

p;     

    rulebasedcollator(locale locale) {  

        m_collator_ = nativecollation.opencollator(locale.tostring());  

static native int opencollator(string locale);  

這就是上面給出的單例模式e,可帶參數的單例模式

檔案:frameworks/base/core/java/android/text/editable.java

private static editable.factory sinstance = new editable.factory();  

/** 

 * returns the standard editable factory. 

 */  

public static editable.factory getinstance() {  

    return sinstance;  

可見這是單例模式d是執行個體應用

檔案:frameworks/base/core/java/android/view/accessibility/accessibilitymanager.java

public static accessibilitymanager getinstance(context context) {  

    synchronized (sinstancesync) {  

        if (sinstance == null) {  

            sinstance = new accessibilitymanager(context);  

這是單例模式c的應用。

android使用單例模式的地方很多,特别是資料庫建立時,就會使用到單例模式。因每種單例模式試用場景不一樣,是以android在不同地方使用了不同的單例模式實作方式。