您好,登錄后才能下訂單哦!
本文實例講述了Android編程設計模式之單例模式。分享給大家供大家參考,具體如下:
一、介紹
單例模式是應用最廣的模式之一,也可能是很多初級工程師唯一會使用的設計模式。在應用這個模式時,單例對象的類必須保證只有一個實例存在。許多時候整個系統只需要擁有一個全局對象,這樣有利于我們協調系統整體的行為。
二、定義
確保某一個類只有一個實例,而且自行實例化并向整個系統提供這個實例。
三、使用場景
確保某個類有且只有一個對象的場景,避免產生多個對象消耗過多的資源,或者某種類型的對象只應該有且只有一個。例如,創建一個對象需要消耗的資源過多,如要訪問IO和數據庫等資源,這時就要考慮使用單例模式。
四、實現方式
1、餓漢模式
示例代碼:
/** * 餓漢模式 */ public class Singleton { private static Singleton instance; private Singleton(){} public static Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } }
優點:延遲加載(需要的時候才去加載)
缺點:線程不安全,在多線程中很容易出現不同步的情況,如在數據庫對象進行的頻繁讀寫操作時。
2、懶漢模式
示例代碼:
/** * 懶漢模式 */ public class Singleton { private static Singleton instance; private Singleton(){} public static synchronized Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } }
與餓漢模式相比,getInstance()方法中添加了synchronized關鍵字,也就是說getInstance是一個同步方法,這就在多線程的情況下保證單例對象的唯一性的手段。但是,細想一下,大家可能會發現一個問題,即使instance已經被初始化(第一次調用的時候就會被初始化instance),每次調用getInstance方法都會進行同步,這樣會消耗不必要的資源,這也是懶漢模式存在的最大問題。
優點:解決了線程不安全的問題。
缺點:第一次加載時需要及時進行實例化,反應稍慢,最大問題是每次調用getInstance都進行同步,造成不必要的同步開銷。
補充:在Android源碼中使用的該單例方法有:InputMethodManager,AccessibilityManager等都是使用這種單例模式
3、Double Check Lock(DCL)雙重檢查鎖定
示例代碼:
/** * 雙重檢查鎖定(DCL)單例模式 */ 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; } }
本程序的亮點自然在getInstance方法上,可以看到getInstance方法中對instance進行了兩次判空:第一層判斷主要是為了避免不必要的同步,第二層的判斷則是為了在null的情況下創建實例。
假設線程A執行到instance = new Singleton()
語句,這里看起來是一句代碼,但實際上它并不是一個原子操作,這句代碼最終會被編譯成多條匯編指令,它大致做了3件事情:
(1)個Singleton的實例分配內存;
(2)調用Singleton()
的構造函數,初始化成員字段;
(3)將instance對象指向分配的內存空間(此時instance就不是null了)。
但是,由于java編譯器允許處理器亂序執行,以及JDK1.5之前JMM(Java Memory Model,即Java內存模型)中Cache、寄存器到主內存回寫順序的規定,上面的第二和第三句的順序是無法保證的。也就是說,執行順序可能是1-2-3也可能是1-3-2。如果是后者,并且在3執行完畢、2未執行之前,被切換到線程B上,這時候instance因為已經在線程A內執行過了第三點,instance已經是非空了,所有,線程B直接取走了instance,再使用時就會出錯,這就是DCL失效問題,而且這種難以跟蹤難以重現的錯誤很可能會隱藏很久。
在JDK1.5之后,SUN官方已經注意到這種問題,調整了JVM,具體化了volatile關鍵字,因此,如果JDK是1.5或之后的版本,只需要將instance的定義改成private volatile static Singleton instance就可以保證instance對象每次都是從主內存中讀取,就可以使用DCL的寫法來完成單例模式。當然,volatile或多或少也會影響到性能,但考慮到程序的正確性,犧牲這點性能還是值得的。
優點:資源利用率高,第一次執行getInstance時單例對象才會被實例化,效率高。在并發量不多,安全性不高的情況下或許能很完美運行單例模式
缺點:第一次加載時反應稍慢,也由于Java內存模型的原因偶爾會失敗。在高并發環境下也有一定的缺陷,雖然發生概率很小。
補充:在android圖像開源項目Android-Universal-Image-Loader (https://github.com/nostra13/Android-Universal-Image-Loader)中使用的是這種方式。
DCL模式是使用最多的單例實現方式,它能夠在需要時才實例化單例對象,并且能夠在絕大多數場景下保證單例對象的唯一性,除非你的代碼在并發場景比較復雜或者低于JDK6版本下使用,否則,這種方式一般能夠滿足需要。
4、靜態內部類單例模式
DCL雖然在一定程度上解決了資源消耗、多余的同步、線程安全等問題,但是,它還是在某些情況下出現失效的問題。這個問題被稱為雙重檢查鎖定(DCL)失效,在《Java并發編程實踐》一書的最后談到了這個問題,并指出這種“優化”是丑陋的,不贊成使用。而建議使用如下的代碼替代:
示例代碼:
/** * 靜態內部類單例模式 */ public class Singleton { private Singleton(){} public static Singleton getInstance(){ return SingletonHolder.instance; } /** * 靜態內部類 * 延遲加載,減少內存開銷 */ private static class SingletonHolder{ private static final Singleton instance = new Singleton(); } }
當第一次加載Singleton類時并不會初始化instance,只有在第一次調用Singleton的getInstance方法時才會導致instance被初始化。因此,第一次調用getInstance方法會導致虛擬機加載SingletonHolder類,這種方式不僅能夠確保線程安全,也能夠保證單例對象的唯一性,同時也延遲了單例的實例化,所以這是推薦使用的單例模式實現方式。
優點:延遲加載,線程安全(java中class加載時互斥的),也減少了內存消耗
5、枚舉單例
前面講解了一些單例模式實現方式,但是,這些實現方式不是稍顯麻煩就是會在某些情況下出現問題。
示例代碼:
/** * 枚舉單例模式 */ public enum Singleton { /** * 1.從Java1.5開始支持; * 2.無償提供序列化機制; * 3.絕對防止多次實例化,即使在面對復雜的序列化或者反射攻擊的時候; */ instance; private String others; Singleton() { } public String getOthers() { return others; } public void setOthers(String others) { this.others = others; } }
寫法簡單是枚舉單例最大的優點,枚舉在Java中與普通的類是一樣的,不僅能夠有字段,還能有自己的方法。最重要的是默認枚舉實例的創建是線程安全的,并且在任何情況下它都是一個單例。
為什么這么說呢?在上述的幾種單例模式實現中,在一個情況下它們會出現重新創建對象的情況,那就是反序列化。
通過序列化可以將一個單例的實例對象寫到磁盤,然后在讀回來,從而有效的獲得一個實例。即使構造函數是私有的,反序列化時依然可以通過特殊的途徑去創建類的一個新的實例,相當于調用該類的構造函數。反序列化操作提供了一個特別的鉤子函數,類中具有一個私有的、被實例化的方法readResolve()
,這個方法可以讓開發人員控制對象的反序列化。例如,上述幾個示例中如果要杜絕單例對象在被反序列化時重新生成對象,那么必須加入如下方法:
private Object readResolve() throws ObjectStreamException { return instance; }
也就是在readResolve方法中將instance對象返回,而不是默認的重新生成一個新的對象。而對于枚舉,并不存在這個問題,因為即使反序列化它也不會重新生成新的實例。
優點:無償提供序列化機制,絕對防止多次實例化,即使在面對復雜的序列化或者反射攻擊的時候。
缺點:從Java1.5開始支持。
上面主要講了單例模式5種創建方法,大家可以根據其優缺點進行個人實際項目中的使用。
更多關于Android相關內容感興趣的讀者可查看本站專題:《Android開發入門與進階教程》、《Android調試技巧與常見問題解決方法匯總》、《Android基本組件用法總結》、《Android視圖View技巧總結》、《Android布局layout技巧總結》及《Android控件用法總結》
希望本文所述對大家Android程序設計有所幫助。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。